Move altosui to the top level, placing libaltos inside it.
authorKeith Packard <keithp@keithp.com>
Wed, 24 Nov 2010 22:57:57 +0000 (14:57 -0800)
committerKeith Packard <keithp@keithp.com>
Wed, 24 Nov 2010 23:09:05 +0000 (15:09 -0800)
Signed-off-by: Keith Packard <keithp@keithp.com>
204 files changed:
altosui/.gitignore [new file with mode: 0644]
altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml [new file with mode: 0644]
altosui/AltOS Package Configuration.pmdoc/01altosui.xml [new file with mode: 0644]
altosui/AltOS Package Configuration.pmdoc/index.xml [new file with mode: 0644]
altosui/Altos.java [new file with mode: 0644]
altosui/AltosAscent.java [new file with mode: 0644]
altosui/AltosCRCException.java [new file with mode: 0644]
altosui/AltosCSV.java [new file with mode: 0644]
altosui/AltosCSVUI.java [new file with mode: 0644]
altosui/AltosChannelMenu.java [new file with mode: 0644]
altosui/AltosConfig.java [new file with mode: 0644]
altosui/AltosConfigUI.java [new file with mode: 0644]
altosui/AltosConfigureUI.java [new file with mode: 0644]
altosui/AltosConvert.java [new file with mode: 0644]
altosui/AltosDataChooser.java [new file with mode: 0644]
altosui/AltosDataPoint.java [new file with mode: 0644]
altosui/AltosDataPointReader.java [new file with mode: 0644]
altosui/AltosDebug.java [new file with mode: 0644]
altosui/AltosDescent.java [new file with mode: 0644]
altosui/AltosDevice.java [new file with mode: 0644]
altosui/AltosDeviceDialog.java [new file with mode: 0644]
altosui/AltosDisplayThread.java [new file with mode: 0644]
altosui/AltosEepromDownload.java [new file with mode: 0644]
altosui/AltosEepromIterable.java [new file with mode: 0644]
altosui/AltosEepromMonitor.java [new file with mode: 0644]
altosui/AltosEepromRecord.java [new file with mode: 0644]
altosui/AltosFile.java [new file with mode: 0644]
altosui/AltosFlash.java [new file with mode: 0644]
altosui/AltosFlashUI.java [new file with mode: 0644]
altosui/AltosFlightDisplay.java [new file with mode: 0644]
altosui/AltosFlightInfoTableModel.java [new file with mode: 0644]
altosui/AltosFlightReader.java [new file with mode: 0644]
altosui/AltosFlightStatus.java [new file with mode: 0644]
altosui/AltosFlightStatusTableModel.java [new file with mode: 0644]
altosui/AltosFlightUI.java [new file with mode: 0644]
altosui/AltosGPS.java [new file with mode: 0644]
altosui/AltosGraph.java [new file with mode: 0644]
altosui/AltosGraphTime.java [new file with mode: 0644]
altosui/AltosGraphUI.java [new file with mode: 0644]
altosui/AltosGreatCircle.java [new file with mode: 0644]
altosui/AltosHexfile.java [new file with mode: 0644]
altosui/AltosIgnite.java [new file with mode: 0644]
altosui/AltosIgniteUI.java [new file with mode: 0644]
altosui/AltosInfoTable.java [new file with mode: 0644]
altosui/AltosKML.java [new file with mode: 0644]
altosui/AltosLanded.java [new file with mode: 0644]
altosui/AltosLed.java [new file with mode: 0644]
altosui/AltosLights.java [new file with mode: 0644]
altosui/AltosLine.java [new file with mode: 0644]
altosui/AltosLog.java [new file with mode: 0644]
altosui/AltosPad.java [new file with mode: 0644]
altosui/AltosParse.java [new file with mode: 0644]
altosui/AltosPreferences.java [new file with mode: 0644]
altosui/AltosReader.java [new file with mode: 0644]
altosui/AltosRecord.java [new file with mode: 0644]
altosui/AltosRecordIterable.java [new file with mode: 0644]
altosui/AltosReplayReader.java [new file with mode: 0644]
altosui/AltosRomconfig.java [new file with mode: 0644]
altosui/AltosRomconfigUI.java [new file with mode: 0644]
altosui/AltosSerial.java [new file with mode: 0644]
altosui/AltosSerialInUseException.java [new file with mode: 0644]
altosui/AltosSerialMonitor.java [new file with mode: 0644]
altosui/AltosSiteMap.java [new file with mode: 0644]
altosui/AltosSiteMapCache.java [new file with mode: 0644]
altosui/AltosSiteMapTile.java [new file with mode: 0644]
altosui/AltosState.java [new file with mode: 0644]
altosui/AltosTelemetry.java [new file with mode: 0644]
altosui/AltosTelemetryIterable.java [new file with mode: 0644]
altosui/AltosTelemetryReader.java [new file with mode: 0644]
altosui/AltosUI.app/Contents/Info.plist [new file with mode: 0644]
altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub [new file with mode: 0755]
altosui/AltosUI.app/Contents/PkgInfo [new file with mode: 0644]
altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns [new file with mode: 0644]
altosui/AltosUI.java [new file with mode: 0644]
altosui/AltosVoice.java [new file with mode: 0644]
altosui/AltosWriter.java [new file with mode: 0644]
altosui/GrabNDrag.java [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf [new file with mode: 0644]
altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys [new file with mode: 0644]
altosui/Instdrv/NSIS/Plugins/InstDrv.dll [new file with mode: 0644]
altosui/Makefile-standalone [new file with mode: 0644]
altosui/Makefile.am [new file with mode: 0644]
altosui/altos-windows.nsi [new file with mode: 0644]
altosui/altosui-fat [new file with mode: 0755]
altosui/altosui.1 [new file with mode: 0644]
altosui/altusmetrum.jpg [new file with mode: 0644]
altosui/libaltos/.gitignore [new file with mode: 0644]
altosui/libaltos/Makefile-standalone [new file with mode: 0644]
altosui/libaltos/Makefile.am [new file with mode: 0644]
altosui/libaltos/altos.dll [new file with mode: 0755]
altosui/libaltos/cjnitest.c [new file with mode: 0644]
altosui/libaltos/libaltos.c [new file with mode: 0644]
altosui/libaltos/libaltos.dylib [new file with mode: 0755]
altosui/libaltos/libaltos.h [new file with mode: 0644]
altosui/libaltos/libaltos.i0 [new file with mode: 0644]
ao-tools/Makefile.am
ao-tools/altosui/.gitignore [deleted file]
ao-tools/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml [deleted file]
ao-tools/altosui/AltOS Package Configuration.pmdoc/01altosui.xml [deleted file]
ao-tools/altosui/AltOS Package Configuration.pmdoc/index.xml [deleted file]
ao-tools/altosui/Altos.java [deleted file]
ao-tools/altosui/AltosAscent.java [deleted file]
ao-tools/altosui/AltosCRCException.java [deleted file]
ao-tools/altosui/AltosCSV.java [deleted file]
ao-tools/altosui/AltosCSVUI.java [deleted file]
ao-tools/altosui/AltosChannelMenu.java [deleted file]
ao-tools/altosui/AltosConfig.java [deleted file]
ao-tools/altosui/AltosConfigUI.java [deleted file]
ao-tools/altosui/AltosConfigureUI.java [deleted file]
ao-tools/altosui/AltosConvert.java [deleted file]
ao-tools/altosui/AltosDataChooser.java [deleted file]
ao-tools/altosui/AltosDataPoint.java [deleted file]
ao-tools/altosui/AltosDataPointReader.java [deleted file]
ao-tools/altosui/AltosDebug.java [deleted file]
ao-tools/altosui/AltosDescent.java [deleted file]
ao-tools/altosui/AltosDevice.java [deleted file]
ao-tools/altosui/AltosDeviceDialog.java [deleted file]
ao-tools/altosui/AltosDisplayThread.java [deleted file]
ao-tools/altosui/AltosEepromDownload.java [deleted file]
ao-tools/altosui/AltosEepromIterable.java [deleted file]
ao-tools/altosui/AltosEepromMonitor.java [deleted file]
ao-tools/altosui/AltosEepromRecord.java [deleted file]
ao-tools/altosui/AltosFile.java [deleted file]
ao-tools/altosui/AltosFlash.java [deleted file]
ao-tools/altosui/AltosFlashUI.java [deleted file]
ao-tools/altosui/AltosFlightDisplay.java [deleted file]
ao-tools/altosui/AltosFlightInfoTableModel.java [deleted file]
ao-tools/altosui/AltosFlightReader.java [deleted file]
ao-tools/altosui/AltosFlightStatus.java [deleted file]
ao-tools/altosui/AltosFlightStatusTableModel.java [deleted file]
ao-tools/altosui/AltosFlightUI.java [deleted file]
ao-tools/altosui/AltosGPS.java [deleted file]
ao-tools/altosui/AltosGraph.java [deleted file]
ao-tools/altosui/AltosGraphTime.java [deleted file]
ao-tools/altosui/AltosGraphUI.java [deleted file]
ao-tools/altosui/AltosGreatCircle.java [deleted file]
ao-tools/altosui/AltosHexfile.java [deleted file]
ao-tools/altosui/AltosIgnite.java [deleted file]
ao-tools/altosui/AltosIgniteUI.java [deleted file]
ao-tools/altosui/AltosInfoTable.java [deleted file]
ao-tools/altosui/AltosKML.java [deleted file]
ao-tools/altosui/AltosLanded.java [deleted file]
ao-tools/altosui/AltosLed.java [deleted file]
ao-tools/altosui/AltosLights.java [deleted file]
ao-tools/altosui/AltosLine.java [deleted file]
ao-tools/altosui/AltosLog.java [deleted file]
ao-tools/altosui/AltosPad.java [deleted file]
ao-tools/altosui/AltosParse.java [deleted file]
ao-tools/altosui/AltosPreferences.java [deleted file]
ao-tools/altosui/AltosReader.java [deleted file]
ao-tools/altosui/AltosRecord.java [deleted file]
ao-tools/altosui/AltosRecordIterable.java [deleted file]
ao-tools/altosui/AltosReplayReader.java [deleted file]
ao-tools/altosui/AltosRomconfig.java [deleted file]
ao-tools/altosui/AltosRomconfigUI.java [deleted file]
ao-tools/altosui/AltosSerial.java [deleted file]
ao-tools/altosui/AltosSerialInUseException.java [deleted file]
ao-tools/altosui/AltosSerialMonitor.java [deleted file]
ao-tools/altosui/AltosSiteMap.java [deleted file]
ao-tools/altosui/AltosSiteMapCache.java [deleted file]
ao-tools/altosui/AltosSiteMapTile.java [deleted file]
ao-tools/altosui/AltosState.java [deleted file]
ao-tools/altosui/AltosTelemetry.java [deleted file]
ao-tools/altosui/AltosTelemetryIterable.java [deleted file]
ao-tools/altosui/AltosTelemetryReader.java [deleted file]
ao-tools/altosui/AltosUI.app/Contents/Info.plist [deleted file]
ao-tools/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub [deleted file]
ao-tools/altosui/AltosUI.app/Contents/PkgInfo [deleted file]
ao-tools/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns [deleted file]
ao-tools/altosui/AltosUI.java [deleted file]
ao-tools/altosui/AltosVoice.java [deleted file]
ao-tools/altosui/AltosWriter.java [deleted file]
ao-tools/altosui/GrabNDrag.java [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf [deleted file]
ao-tools/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys [deleted file]
ao-tools/altosui/Instdrv/NSIS/Plugins/InstDrv.dll [deleted file]
ao-tools/altosui/Makefile-standalone [deleted file]
ao-tools/altosui/Makefile.am [deleted file]
ao-tools/altosui/altos-windows.nsi [deleted file]
ao-tools/altosui/altosui-fat [deleted file]
ao-tools/altosui/altosui.1 [deleted file]
ao-tools/altosui/altusmetrum.jpg [deleted file]
ao-tools/libaltos/.gitignore [deleted file]
ao-tools/libaltos/Makefile-standalone [deleted file]
ao-tools/libaltos/Makefile.am [deleted file]
ao-tools/libaltos/altos.dll [deleted file]
ao-tools/libaltos/cjnitest.c [deleted file]
ao-tools/libaltos/libaltos.c [deleted file]
ao-tools/libaltos/libaltos.dylib [deleted file]
ao-tools/libaltos/libaltos.h [deleted file]
ao-tools/libaltos/libaltos.i0 [deleted file]
configure.ac

diff --git a/altosui/.gitignore b/altosui/.gitignore
new file mode 100644 (file)
index 0000000..89be1d5
--- /dev/null
@@ -0,0 +1,19 @@
+windows/
+linux/
+macosx/
+fat/
+Manifest.txt
+Manifest-fat.txt
+libaltosJNI
+classes
+altosui
+altosui-test
+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 (file)
index 0000000..18e00fe
--- /dev/null
@@ -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 (file)
index 0000000..6170931
--- /dev/null
@@ -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 (file)
index 0000000..fabe54a
--- /dev/null
@@ -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 (file)
index 0000000..8ee94e0
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.util.*;
+import java.text.*;
+
+public class Altos {
+       /* EEProm command letters */
+       static final int AO_LOG_FLIGHT = 'F';
+       static final int AO_LOG_SENSOR = 'A';
+       static final int AO_LOG_TEMP_VOLT = 'T';
+       static final int AO_LOG_DEPLOY = 'D';
+       static final int AO_LOG_STATE = 'S';
+       static final int AO_LOG_GPS_TIME = 'G';
+       static final int AO_LOG_GPS_LAT = 'N';
+       static final int AO_LOG_GPS_LON = 'W';
+       static final int AO_LOG_GPS_ALT = 'H';
+       static final int AO_LOG_GPS_SAT = 'V';
+       static final int AO_LOG_GPS_DATE = 'Y';
+
+       /* Added for header fields in eeprom files */
+       static final int AO_LOG_CONFIG_VERSION = 1000;
+       static final int AO_LOG_MAIN_DEPLOY = 1001;
+       static final int AO_LOG_APOGEE_DELAY = 1002;
+       static final int AO_LOG_RADIO_CHANNEL = 1003;
+       static final int AO_LOG_CALLSIGN = 1004;
+       static final int AO_LOG_ACCEL_CAL = 1005;
+       static final int AO_LOG_RADIO_CAL = 1006;
+       static final int AO_LOG_MANUFACTURER = 1007;
+       static final int AO_LOG_PRODUCT = 1008;
+       static final int AO_LOG_SERIAL_NUMBER = 1009;
+       static final int AO_LOG_SOFTWARE_VERSION = 1010;
+
+       /* Added to flag invalid records */
+       static final int AO_LOG_INVALID = -1;
+
+       /* Flight state numbers and names */
+       static final int ao_flight_startup = 0;
+       static final int ao_flight_idle = 1;
+       static final int ao_flight_pad = 2;
+       static final int ao_flight_boost = 3;
+       static final int ao_flight_fast = 4;
+       static final int ao_flight_coast = 5;
+       static final int ao_flight_drogue = 6;
+       static final int ao_flight_main = 7;
+       static final int ao_flight_landed = 8;
+       static final int ao_flight_invalid = 9;
+
+       static HashMap<String,Integer>  string_to_state = new HashMap<String,Integer>();
+
+       static boolean map_initialized = false;
+
+       static final int tab_elt_pad = 5;
+
+       static final Font label_font = new Font("Dialog", Font.PLAIN, 22);
+       static final Font value_font = new Font("Monospaced", Font.PLAIN, 22);
+       static final Font status_font = new Font("SansSerif", Font.BOLD, 24);
+
+       static final int text_width = 16;
+
+       static void initialize_map()
+       {
+               string_to_state.put("startup", ao_flight_startup);
+               string_to_state.put("idle", ao_flight_idle);
+               string_to_state.put("pad", ao_flight_pad);
+               string_to_state.put("boost", ao_flight_boost);
+               string_to_state.put("fast", ao_flight_fast);
+               string_to_state.put("coast", ao_flight_coast);
+               string_to_state.put("drogue", ao_flight_drogue);
+               string_to_state.put("main", ao_flight_main);
+               string_to_state.put("landed", ao_flight_landed);
+               string_to_state.put("invalid", ao_flight_invalid);
+               map_initialized = true;
+       }
+
+       static String[] state_to_string = {
+               "startup",
+               "idle",
+               "pad",
+               "boost",
+               "fast",
+               "coast",
+               "drogue",
+               "main",
+               "landed",
+               "invalid",
+       };
+
+       static public int state(String state) {
+               if (!map_initialized)
+                       initialize_map();
+               if (string_to_state.containsKey(state))
+                       return string_to_state.get(state);
+               return ao_flight_invalid;
+       }
+
+       static public String state_name(int state) {
+               if (state < 0 || state_to_string.length <= state)
+                       return "invalid";
+               return state_to_string[state];
+       }
+
+       static final int AO_GPS_VALID = (1 << 4);
+       static final int AO_GPS_RUNNING = (1 << 5);
+       static final int AO_GPS_DATE_VALID = (1 << 6);
+       static final int AO_GPS_NUM_SAT_SHIFT = 0;
+       static final int AO_GPS_NUM_SAT_MASK = 0xf;
+
+       static boolean isspace(int c) {
+               switch (c) {
+               case ' ':
+               case '\t':
+                       return true;
+               }
+               return false;
+       }
+
+       static 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;
+       }
+
+       static boolean ishex(String s) {
+               for (int i = 0; i < s.length(); i++)
+                       if (!ishex(s.charAt(i)))
+                               return false;
+               return true;
+       }
+
+       static 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;
+       }
+
+       static int fromhex(String s) throws NumberFormatException {
+               int c, v = 0;
+               for (int i = 0; i < s.length(); i++) {
+                       c = s.charAt(i);
+                       if (!ishex(c)) {
+                               if (i == 0)
+                                       throw new NumberFormatException(String.format("invalid hex \"%s\"", s));
+                               return v;
+                       }
+                       v = v * 16 + fromhex(c);
+               }
+               return v;
+       }
+
+       static boolean isdec(int c) {
+               if ('0' <= c && c <= '9')
+                       return true;
+               return false;
+       }
+
+       static boolean isdec(String s) {
+               for (int i = 0; i < s.length(); i++)
+                       if (!isdec(s.charAt(i)))
+                               return false;
+               return true;
+       }
+
+       static int fromdec(int c) {
+               if ('0' <= c && c <= '9')
+                       return c - '0';
+               return -1;
+       }
+
+       static int fromdec(String s) throws NumberFormatException {
+               int c, v = 0;
+               int sign = 1;
+               for (int i = 0; i < s.length(); i++) {
+                       c = s.charAt(i);
+                       if (i == 0 && c == '-') {
+                               sign = -1;
+                       } else if (!isdec(c)) {
+                               if (i == 0)
+                                       throw new NumberFormatException(String.format("invalid number \"%s\"", s));
+                               return v;
+                       } else
+                               v = v * 10 + fromdec(c);
+               }
+               return v * sign;
+       }
+
+       static String replace_extension(String input, String extension) {
+               int dot = input.lastIndexOf(".");
+               if (dot > 0)
+                       input = input.substring(0,dot);
+               return input.concat(extension);
+       }
+}
diff --git a/altosui/AltosAscent.java b/altosui/AltosAscent.java
new file mode 100644 (file)
index 0000000..64bdcf3
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosAscent extends JComponent implements AltosFlightDisplay {
+       GridBagLayout   layout;
+
+       public class AscentStatus {
+               JLabel          label;
+               JTextField      value;
+               AltosLights     lights;
+
+               void show(AltosState state, int crc_errors) {}
+               void reset() {
+                       value.setText("");
+                       lights.set(false);
+               }
+
+               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("");
+               }
+               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 = 0;
+               }
+
+               void show(String format, double v) {
+                       value.setText(String.format(format, v));
+                       if (v > max) {
+                               max_value.setText(String.format(format, 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("%6.0f m", 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("%6.0f m/s", 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("%6.0f m/s²", 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) {
+                       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) {
+                       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) {
+                       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) {
+                       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 show(AltosState state, int crc_errors) {
+               lat.show(state, crc_errors);
+               lon.show(state, crc_errors);
+               height.show(state, crc_errors);
+               main.show(state, crc_errors);
+               apogee.show(state, crc_errors);
+               speed.show(state, crc_errors);
+               accel.show(state, crc_errors);
+       }
+
+       public void labels(GridBagLayout layout, int y) {
+               GridBagConstraints      c;
+               JLabel                  cur, max;
+
+               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/AltosCRCException.java b/altosui/AltosCRCException.java
new file mode 100644 (file)
index 0000000..4a529bc
--- /dev/null
@@ -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 AltosCRCException extends Exception {
+       public int rssi;
+
+       public AltosCRCException (int in_rssi) {
+               rssi = in_rssi;
+       }
+}
diff --git a/altosui/AltosCSV.java b/altosui/AltosCSV.java
new file mode 100644 (file)
index 0000000..df98b2b
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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.*;
+
+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 = 2;
+
+       /* Version 2 format:
+        *
+        * General info
+        *      version number
+        *      serial number
+        *      flight number
+        *      callsign
+        *      time (seconds since boost)
+        *      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)
+        *
+        * GPS data
+        *      connected (1/0)
+        *      locked (1/0)
+        *      nsat (used for solution)
+        *      latitude (°)
+        *      longitude (°)
+        *      altitude (m)
+        *      year (e.g. 2010)
+        *      month (1-12)
+        *      day (1-31)
+        *      hour (0-23)
+        *      minute (0-59)
+        *      second (0-59)
+        *      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
+        */
+
+       void write_general_header() {
+               out.printf("version,serial,flight,call,time,rssi,lqi");
+       }
+
+       void write_general(AltosRecord record) {
+               out.printf("%s, %d, %d, %s, %8.2f, %4d, %3d",
+                          ALTOS_CSV_VERSION, record.serial, record.flight, record.callsign,
+                          (double) record.time,
+                          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_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_header() {
+               out.printf("#"); write_general_header();
+               out.printf(","); write_flight_header();
+               out.printf(","); write_basic_header();
+               out.printf(","); write_gps_header();
+               out.printf(","); write_gps_sat_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(",");
+               write_gps(record); out.printf(",");
+               write_gps_sat(record);
+               out.printf ("\n");
+       }
+
+       void flush_pad() {
+               while (!pad_records.isEmpty()) {
+                       write_one (pad_records.remove());
+               }
+       }
+
+       public void write(AltosRecord record) {
+               if (!header_written) {
+                       write_header();
+                       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(File in_name) throws FileNotFoundException {
+               name = in_name;
+               out = new PrintStream(name);
+               pad_records = new LinkedList<AltosRecord>();
+       }
+
+       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 (file)
index 0000000..e1b6002
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosCSVUI
+       extends JDialog
+       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,
+                                                             file.getName(),
+                                                             "Cannot open file",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+       }
+}
diff --git a/altosui/AltosChannelMenu.java b/altosui/AltosChannelMenu.java
new file mode 100644 (file)
index 0000000..abbb86f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosChannelMenu extends 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/AltosConfig.java b/altosui/AltosConfig.java
new file mode 100644 (file)
index 0000000..1c42870
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * 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.*;
+
+public class AltosConfig implements Runnable, ActionListener {
+
+       class int_ref {
+               int     value;
+
+               public int get() {
+                       return value;
+               }
+               public void set(int i) {
+                       value = i;
+               }
+               public int_ref(int i) {
+                       value = i;
+               }
+       }
+
+       class string_ref {
+               String  value;
+
+               public String get() {
+                       return value;
+               }
+               public void set(String i) {
+                       value = i;
+               }
+               public string_ref(String i) {
+                       value = i;
+               }
+       }
+
+       JFrame          owner;
+       AltosDevice     device;
+       AltosSerial     serial_line;
+       boolean         remote;
+       Thread          config_thread;
+       int_ref         serial;
+       int_ref         main_deploy;
+       int_ref         apogee_delay;
+       int_ref         radio_channel;
+       int_ref         radio_calibration;
+       string_ref      version;
+       string_ref      product;
+       string_ref      callsign;
+       AltosConfigUI   config_ui;
+       boolean         serial_started;
+
+       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 {
+               serial_started = true;
+               if (remote) {
+                       serial_line.set_radio();
+                       serial_line.printf("p\nE 0\n");
+                       serial_line.flush_input();
+               }
+       }
+
+       void stop_serial() throws InterruptedException {
+               if (!serial_started)
+                       return;
+               serial_started = false;
+               if (remote) {
+                       serial_line.printf("~");
+                       serial_line.flush_output();
+               }
+       }
+
+       void get_data() throws InterruptedException, TimeoutException {
+               try {
+                       start_serial();
+                       serial_line.printf("c s\nv\n");
+                       for (;;) {
+                               String line = serial_line.get_reply(5000);
+                               if (line == null)
+                                       throw new TimeoutException();
+                               get_int(line, "serial-number", serial);
+                               get_int(line, "Main deploy:", main_deploy);
+                               get_int(line, "Apogee delay:", apogee_delay);
+                               get_int(line, "Radio channel:", radio_channel);
+                               get_int(line, "Radio cal:", radio_calibration);
+                               get_string(line, "Callsign:", callsign);
+                               get_string(line,"software-version", version);
+                               get_string(line,"product", product);
+
+                               /* signals the end of the version info */
+                               if (line.startsWith("software-version"))
+                                       break;
+                       }
+               } finally {
+                       stop_serial();
+               }
+       }
+
+       void init_ui () throws InterruptedException, TimeoutException {
+               config_ui = new AltosConfigUI(owner, remote);
+               config_ui.addActionListener(this);
+               set_ui();
+       }
+
+       void abort() {
+               JOptionPane.showMessageDialog(owner,
+                                             String.format("Connection to \"%s\" failed",
+                                                           device.toShortString()),
+                                             "Connection Failed",
+                                             JOptionPane.ERROR_MESSAGE);
+               try {
+                       stop_serial();
+               } catch (InterruptedException ie) {
+               }
+               serial_line.close();
+               serial_line = null;
+       }
+
+       void set_ui() throws InterruptedException, TimeoutException {
+               if (serial_line != null)
+                       get_data();
+               config_ui.set_serial(serial.get());
+               config_ui.set_product(product.get());
+               config_ui.set_version(version.get());
+               config_ui.set_main_deploy(main_deploy.get());
+               config_ui.set_apogee_delay(apogee_delay.get());
+               config_ui.set_radio_channel(radio_channel.get());
+               config_ui.set_radio_calibration(radio_calibration.get());
+               config_ui.set_callsign(callsign.get());
+               config_ui.set_clean();
+       }
+
+       void run_dialog() {
+       }
+
+       void save_data() {
+               main_deploy.set(config_ui.main_deploy());
+               apogee_delay.set(config_ui.apogee_delay());
+               radio_channel.set(config_ui.radio_channel());
+               radio_calibration.set(config_ui.radio_calibration());
+               callsign.set(config_ui.callsign());
+               try {
+                       start_serial();
+                       serial_line.printf("c m %d\n", main_deploy.get());
+                       serial_line.printf("c d %d\n", apogee_delay.get());
+                       if (!remote) {
+                               serial_line.printf("c r %d\n", radio_channel.get());
+                               serial_line.printf("c f %d\n", radio_calibration.get());
+                       }
+                       serial_line.printf("c c %s\n", callsign.get());
+                       serial_line.printf("c w\n");
+               } catch (InterruptedException ie) {
+               } finally {
+                       try {
+                               stop_serial();
+                       } catch (InterruptedException ie) {
+                       }
+               }
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               String  cmd = e.getActionCommand();
+               try {
+                       if (cmd.equals("Save")) {
+                               save_data();
+                               set_ui();
+                       } else if (cmd.equals("Reset")) {
+                               set_ui();
+                       } else if (cmd.equals("Reboot")) {
+                               if (serial_line != null) {
+                                       start_serial();
+                                       serial_line.printf("r eboot\n");
+                                       serial_line.flush_output();
+                                       stop_serial();
+                                       serial_line.close();
+                               }
+                       } else if (cmd.equals("Close")) {
+                               if (serial_line != null)
+                                       serial_line.close();
+                       }
+               } catch (InterruptedException ie) {
+                       abort();
+               } catch (TimeoutException te) {
+                       abort();
+               }
+       }
+
+       public void run () {
+               try {
+                       init_ui();
+                       config_ui.make_visible();
+               } catch (InterruptedException ie) {
+                       abort();
+               } catch (TimeoutException te) {
+                       abort();
+               }
+       }
+
+       public AltosConfig(JFrame given_owner) {
+               owner = given_owner;
+
+               serial = new int_ref(0);
+               main_deploy = new int_ref(250);
+               apogee_delay = new int_ref(0);
+               radio_channel = new int_ref(0);
+               radio_calibration = new int_ref(1186611);
+               callsign = new string_ref("N0CALL");
+               version = new string_ref("unknown");
+               product = new string_ref("unknown");
+
+               device = AltosDeviceDialog.show(owner, AltosDevice.product_any);
+               if (device != null) {
+                       try {
+                               serial_line = new AltosSerial(device);
+                               if (!device.matchProduct(AltosDevice.product_telemetrum))
+                                       remote = true;
+                               config_thread = new Thread(this);
+                               config_thread.start();
+                       } catch (FileNotFoundException ee) {
+                               JOptionPane.showMessageDialog(owner,
+                                                             String.format("Cannot open device \"%s\"",
+                                                                           device.toShortString()),
+                                                             "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/AltosConfigUI.java b/altosui/AltosConfigUI.java
new file mode 100644 (file)
index 0000000..cfa5d7b
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * 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.*;
+
+public class AltosConfigUI
+       extends JDialog
+       implements ActionListener, ItemListener, DocumentListener
+{
+
+       Container       pane;
+       Box             box;
+       JLabel          product_label;
+       JLabel          version_label;
+       JLabel          serial_label;
+       JLabel          main_deploy_label;
+       JLabel          apogee_delay_label;
+       JLabel          radio_channel_label;
+       JLabel          radio_calibration_label;
+       JLabel          callsign_label;
+
+       public boolean          dirty;
+
+       JFrame          owner;
+       JLabel          product_value;
+       JLabel          version_value;
+       JLabel          serial_value;
+       JComboBox       main_deploy_value;
+       JComboBox       apogee_delay_value;
+       JComboBox       radio_channel_value;
+       JTextField      radio_calibration_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[] radio_channel_values = new String[10];
+               {
+                       for (int i = 0; i <= 9; i++)
+                               radio_channel_values[i] = String.format("Channel %1d (%7.3fMHz)",
+                                                                       i, 434.550 + i * 0.1);
+               }
+
+       /* A window listener to catch closing events and tell the config code */
+       class ConfigListener extends WindowAdapter {
+               AltosConfigUI   ui;
+
+               public ConfigListener(AltosConfigUI this_ui) {
+                       ui = this_ui;
+               }
+
+               public void windowClosing(WindowEvent e) {
+                       ui.actionPerformed(new ActionEvent(e.getSource(),
+                                                          ActionEvent.ACTION_PERFORMED,
+                                                          "Close"));
+               }
+       }
+
+       /* Build the UI using a grid bag */
+       public AltosConfigUI(JFrame in_owner, boolean remote) {
+               super (in_owner, "Configure TeleMetrum", false);
+
+               owner = in_owner;
+               GridBagConstraints c;
+
+               Insets il = new Insets(4,4,4,4);
+               Insets ir = new Insets(4,4,4,4);
+
+               pane = getContentPane();
+               pane.setLayout(new GridBagLayout());
+
+               /* Product */
+               c = new GridBagConstraints();
+               c.gridx = 0; c.gridy = 0;
+               c.gridwidth = 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);
+
+               /* Main deploy */
+               c = new GridBagConstraints();
+               c.gridx = 0; c.gridy = 3;
+               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 = 3;
+               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);
+
+               /* Apogee delay */
+               c = new GridBagConstraints();
+               c.gridx = 0; c.gridy = 4;
+               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 = 4;
+               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);
+
+               /* Radio channel */
+               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_channel_label = new JLabel("Radio Channel:");
+               pane.add(radio_channel_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_channel_value = new JComboBox(radio_channel_values);
+               radio_channel_value.setEditable(false);
+               radio_channel_value.addItemListener(this);
+               if (remote)
+                       radio_channel_value.setEnabled(false);
+               pane.add(radio_channel_value, c);
+
+               /* 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 JTextField(String.format("%d", 1186611));
+               radio_calibration_value.getDocument().addDocumentListener(this);
+               if (remote)
+                       radio_calibration_value.setEnabled(false);
+               pane.add(radio_calibration_value, c);
+
+               /* Callsign */
+               c = new GridBagConstraints();
+               c.gridx = 0; c.gridy = 7;
+               c.gridwidth = 4;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.LINE_START;
+               c.insets = il;
+               c.ipady = 5;
+               callsign_label = new JLabel("Callsign:");
+               pane.add(callsign_label, c);
+
+               c = new GridBagConstraints();
+               c.gridx = 4; c.gridy = 7;
+               c.gridwidth = 4;
+               c.fill = GridBagConstraints.HORIZONTAL;
+               c.weightx = 1;
+               c.anchor = GridBagConstraints.LINE_START;
+               c.insets = ir;
+               c.ipady = 5;
+               callsign_value = new JTextField(AltosPreferences.callsign());
+               callsign_value.getDocument().addDocumentListener(this);
+               pane.add(callsign_value, c);
+
+               /* Buttons */
+               c = new GridBagConstraints();
+               c.gridx = 0; c.gridy = 8;
+               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 = 8;
+               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 = 8;
+               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 = 8;
+               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) {
+               product_value.setText(product);
+       }
+
+       public void set_version(String version) {
+               version_value.setText(version);
+       }
+
+       public void set_serial(int serial) {
+               serial_value.setText(String.format("%d", serial));
+       }
+
+       public void set_main_deploy(int new_main_deploy) {
+               main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy));
+       }
+
+       public int main_deploy() {
+               return Integer.parseInt(main_deploy_value.getSelectedItem().toString());
+       }
+
+       public void set_apogee_delay(int new_apogee_delay) {
+               apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay));
+       }
+
+       public int apogee_delay() {
+               return Integer.parseInt(apogee_delay_value.getSelectedItem().toString());
+       }
+
+       public void set_radio_channel(int new_radio_channel) {
+               radio_channel_value.setSelectedIndex(new_radio_channel);
+       }
+
+       public int radio_channel() {
+               return radio_channel_value.getSelectedIndex();
+       }
+
+       public void set_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_callsign(String new_callsign) {
+               callsign_value.setText(new_callsign);
+       }
+
+       public String callsign() {
+               return callsign_value.getText();
+       }
+
+       public void set_clean() {
+               dirty = false;
+       }
+
+ }
\ No newline at end of file
diff --git a/altosui/AltosConfigureUI.java b/altosui/AltosConfigureUI.java
new file mode 100644 (file)
index 0000000..153c59f
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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;
+
+public class AltosConfigureUI
+       extends JDialog
+       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;
+
+       /* DocumentListener interface methods */
+       public void changedUpdate(DocumentEvent e) {
+               AltosPreferences.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);
+
+               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 = 0;
+               c.gridwidth = 3;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.CENTER;
+               pane.add(new JLabel ("Configure AltOS UI"), c);
+
+               /* Voice settings */
+               c.gridx = 0;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Voice"), c);
+
+               enable_voice = new JRadioButton("Enable", AltosPreferences.voice());
+               enable_voice.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       JRadioButton item = (JRadioButton) e.getSource();
+                                       boolean enabled = item.isSelected();
+                                       AltosPreferences.set_voice(enabled);
+                                       if (enabled)
+                                               voice.speak_always("Enable voice.");
+                                       else
+                                               voice.speak_always("Disable voice.");
+                               }
+                       });
+               c.gridx = 1;
+               c.gridy = 1;
+               c.gridwidth = 1;
+               c.weightx = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(enable_voice, c);
+
+               c.gridx = 2;
+               c.gridy = 1;
+               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);
+
+               /* Log directory settings */
+               c.gridx = 0;
+               c.gridy = 2;
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Log Directory"), c);
+
+               configure_log = new JButton(AltosPreferences.logdir().getPath());
+               configure_log.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       AltosPreferences.ConfigureLog();
+                                       configure_log.setText(AltosPreferences.logdir().getPath());
+                               }
+                       });
+               c.gridx = 1;
+               c.gridy = 2;
+               c.gridwidth = 2;
+               c.fill = GridBagConstraints.BOTH;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(configure_log, c);
+
+               /* Callsign setting */
+               c.gridx = 0;
+               c.gridy = 3;
+               c.gridwidth = 1;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(new JLabel("Callsign"), c);
+
+               callsign_value = new JTextField(AltosPreferences.callsign());
+               callsign_value.getDocument().addDocumentListener(this);
+               c.gridx = 1;
+               c.gridy = 3;
+               c.gridwidth = 2;
+               c.fill = GridBagConstraints.BOTH;
+               c.anchor = GridBagConstraints.WEST;
+               pane.add(callsign_value, 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 = 4;
+               c.gridwidth = 3;
+               c.fill = GridBagConstraints.NONE;
+               c.anchor = GridBagConstraints.CENTER;
+               pane.add(close, c);
+
+               pack();
+               setLocationRelativeTo(owner);
+               setVisible(true);
+       }
+}
diff --git a/altosui/AltosConvert.java b/altosui/AltosConvert.java
new file mode 100644 (file)
index 0000000..8cc1df2
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/*
+ * Sensor data conversion functions
+ */
+package altosui;
+
+public class AltosConvert {
+       /*
+        * Pressure Sensor Model, version 1.1
+        *
+        * written by Holly Grimes
+        *
+        * Uses the International Standard Atmosphere as described in
+        *   "A Quick Derivation relating altitude to air pressure" (version 1.03)
+        *    from the Portland State Aerospace Society, except that the atmosphere
+        *    is divided into layers with each layer having a different lapse rate.
+        *
+        * Lapse rate data for each layer was obtained from Wikipedia on Sept. 1, 2007
+        *    at site <http://en.wikipedia.org/wiki/International_Standard_Atmosphere
+        *
+        * Height measurements use the local tangent plane.  The postive z-direction is up.
+        *
+        * All measurements are given in SI units (Kelvin, Pascal, meter, meters/second^2).
+        *   The lapse rate is given in Kelvin/meter, the gas constant for air is given
+        *   in Joules/(kilogram-Kelvin).
+        */
+
+       static final double GRAVITATIONAL_ACCELERATION = -9.80665;
+       static final double AIR_GAS_CONSTANT            = 287.053;
+       static final double NUMBER_OF_LAYERS            = 7;
+       static final double MAXIMUM_ALTITUDE            = 84852.0;
+       static final double MINIMUM_PRESSURE            = 0.3734;
+       static final double LAYER0_BASE_TEMPERATURE     = 288.15;
+       static final double LAYER0_BASE_PRESSURE        = 101325;
+
+       /* lapse rate and base altitude for each layer in the atmosphere */
+       static final double[] lapse_rate = {
+               -0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002
+       };
+
+       static final int[] base_altitude = {
+               0, 11000, 20000, 32000, 47000, 51000, 71000
+       };
+
+       /* outputs atmospheric pressure associated with the given altitude.
+        * altitudes are measured with respect to the mean sea level
+        */
+       static double
+       altitude_to_pressure(double altitude)
+       {
+               double base_temperature = LAYER0_BASE_TEMPERATURE;
+               double base_pressure = LAYER0_BASE_PRESSURE;
+
+               double pressure;
+               double base; /* base for function to determine pressure */
+               double exponent; /* exponent for function to determine pressure */
+               int layer_number; /* identifies layer in the atmosphere */
+               double delta_z; /* difference between two altitudes */
+
+               if (altitude > MAXIMUM_ALTITUDE) /* FIX ME: use sensor data to improve model */
+                       return 0;
+
+               /* calculate the base temperature and pressure for the atmospheric layer
+                  associated with the inputted altitude */
+               for(layer_number = 0; layer_number < NUMBER_OF_LAYERS - 1 && altitude > base_altitude[layer_number + 1]; layer_number++) {
+                       delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
+                       if (lapse_rate[layer_number] == 0.0) {
+                               exponent = GRAVITATIONAL_ACCELERATION * delta_z
+                                       / AIR_GAS_CONSTANT / base_temperature;
+                               base_pressure *= Math.exp(exponent);
+                       }
+                       else {
+                               base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+                               exponent = GRAVITATIONAL_ACCELERATION /
+                                       (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+                               base_pressure *= Math.pow(base, exponent);
+                       }
+                       base_temperature += delta_z * lapse_rate[layer_number];
+               }
+
+               /* calculate the pressure at the inputted altitude */
+               delta_z = altitude - base_altitude[layer_number];
+               if (lapse_rate[layer_number] == 0.0) {
+                       exponent = GRAVITATIONAL_ACCELERATION * delta_z
+                               / AIR_GAS_CONSTANT / base_temperature;
+                       pressure = base_pressure * Math.exp(exponent);
+               }
+               else {
+                       base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+                       exponent = GRAVITATIONAL_ACCELERATION /
+                               (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+                       pressure = base_pressure * Math.pow(base, exponent);
+               }
+
+               return pressure;
+       }
+
+
+/* outputs the altitude associated with the given pressure. the altitude
+   returned is measured with respect to the mean sea level */
+       static double
+       pressure_to_altitude(double pressure)
+       {
+
+               double next_base_temperature = LAYER0_BASE_TEMPERATURE;
+               double next_base_pressure = LAYER0_BASE_PRESSURE;
+
+               double altitude;
+               double base_pressure;
+               double base_temperature;
+               double base; /* base for function to determine base pressure of next layer */
+               double exponent; /* exponent for function to determine base pressure
+                                   of next layer */
+               double coefficient;
+               int layer_number; /* identifies layer in the atmosphere */
+               int delta_z; /* difference between two altitudes */
+
+               if (pressure < 0)  /* illegal pressure */
+                       return -1;
+               if (pressure < MINIMUM_PRESSURE) /* FIX ME: use sensor data to improve model */
+                       return MAXIMUM_ALTITUDE;
+
+               /* calculate the base temperature and pressure for the atmospheric layer
+                  associated with the inputted pressure. */
+               layer_number = -1;
+               do {
+                       layer_number++;
+                       base_pressure = next_base_pressure;
+                       base_temperature = next_base_temperature;
+                       delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
+                       if (lapse_rate[layer_number] == 0.0) {
+                               exponent = GRAVITATIONAL_ACCELERATION * delta_z
+                                       / AIR_GAS_CONSTANT / base_temperature;
+                               next_base_pressure *= Math.exp(exponent);
+                       }
+                       else {
+                               base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+                               exponent = GRAVITATIONAL_ACCELERATION /
+                                       (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+                               next_base_pressure *= Math.pow(base, exponent);
+                       }
+                       next_base_temperature += delta_z * lapse_rate[layer_number];
+               }
+               while(layer_number < NUMBER_OF_LAYERS - 1 && pressure < next_base_pressure);
+
+               /* calculate the altitude associated with the inputted pressure */
+               if (lapse_rate[layer_number] == 0.0) {
+                       coefficient = (AIR_GAS_CONSTANT / GRAVITATIONAL_ACCELERATION)
+                               * base_temperature;
+                       altitude = base_altitude[layer_number]
+                               + coefficient * Math.log(pressure / base_pressure);
+               }
+               else {
+                       base = pressure / base_pressure;
+                       exponent = AIR_GAS_CONSTANT * lapse_rate[layer_number]
+                               / GRAVITATIONAL_ACCELERATION;
+                       coefficient = base_temperature / lapse_rate[layer_number];
+                       altitude = base_altitude[layer_number]
+                               + coefficient * (Math.pow(base, exponent) - 1);
+               }
+
+               return altitude;
+       }
+
+       static double
+       cc_battery_to_voltage(double battery)
+       {
+               return battery / 32767.0 * 5.0;
+       }
+
+       static double
+       cc_ignitor_to_voltage(double ignite)
+       {
+               return ignite / 32767 * 15.0;
+       }
+}
diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java
new file mode 100644 (file)
index 0000000..15de05c
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.*;
+
+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 {
+                                       throw new FileNotFoundException();
+                               }
+                       } catch (FileNotFoundException fe) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             filename,
+                                                             "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"));
+               setCurrentDirectory(AltosPreferences.logdir());
+       }
+}
diff --git a/altosui/AltosDataPoint.java b/altosui/AltosDataPoint.java
new file mode 100644 (file)
index 0000000..66313e0
--- /dev/null
@@ -0,0 +1,29 @@
+
+// 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();
+}
+
diff --git a/altosui/AltosDataPointReader.java b/altosui/AltosDataPointReader.java
new file mode 100644 (file)
index 0000000..7704310
--- /dev/null
@@ -0,0 +1,72 @@
+
+// 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;
+
+class AltosDataPointReader implements Iterable<AltosDataPoint> {
+    Iterator<AltosRecord> iter;
+    AltosState state;
+    AltosRecord record;
+
+    public AltosDataPointReader(Iterable<AltosRecord> reader) {
+        this.iter = reader.iterator();
+        this.state = null;
+    }
+
+    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 Iterator<AltosDataPoint> iterator() {
+        return new Iterator<AltosDataPoint>() {
+            public void remove() { 
+                throw new UnsupportedOperationException(); 
+            }
+            public boolean hasNext() {
+                return iter.hasNext();
+            }
+            public AltosDataPoint next() {
+                read_next_record();
+                return current_dp();
+            }
+        };
+    }
+}
+
diff --git a/altosui/AltosDebug.java b/altosui/AltosDebug.java
new file mode 100644 (file)
index 0000000..8d435b6
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * 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 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");
+                       flush_input();
+                       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().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 {
+                                       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 (file)
index 0000000..16ccd45
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class 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 reset() {
+                       value.setText("");
+                       lights.set(false);
+               }
+
+               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(String format, double v) {
+                       value.setText(String.format(format, v));
+               }
+
+               void show(String v) {
+                       value.setText(v);
+               }
+
+               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("");
+               }
+
+               abstract void show(AltosState state, int crc_errors);
+               void show(String v1, String v2) {
+                       value1.setText(v1);
+                       value2.setText(v2);
+               }
+               void show(String f1, double v1, String f2, double v2) {
+                       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("%6.0f m", 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("%6.0f m/s", 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)
+                               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)
+                               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) {
+                       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) {
+                       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("%6.0f m", 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 show(AltosState state, int crc_errors) {
+               height.show(state, crc_errors);
+               speed.show(state, crc_errors);
+               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);
+               main.show(state, crc_errors);
+               apogee.show(state, crc_errors);
+       }
+
+       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 (file)
index 0000000..f0fda57
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * 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 AltosDevice extends altos_device {
+
+       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) {
+                               loaded_library = false;
+                       }
+                       initialized = true;
+               }
+               return loaded_library;
+       }
+
+       static int usb_vendor_altusmetrum() {
+               if (load_library())
+                       return libaltosConstants.USB_VENDOR_ALTUSMETRUM;
+               return 0x000a;
+       }
+
+       static int usb_product_altusmetrum() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_ALTUSMETRUM;
+               return 0x000a;
+       }
+
+       static int usb_product_altusmetrum_min() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MIN;
+               return 0x000a;
+       }
+
+       static int usb_product_altusmetrum_max() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MAX;
+               return 0x000d;
+       }
+
+       static int usb_product_telemetrum() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_TELEMETRUM;
+               return 0x000b;
+       }
+
+       static int usb_product_teledongle() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_TELEDONGLE;
+               return 0x000c;
+       }
+
+       static int usb_product_teleterra() {
+               if (load_library())
+                       return libaltosConstants.USB_PRODUCT_TELETERRA;
+               return 0x000d;
+       }
+
+       public final static int vendor_altusmetrum = usb_vendor_altusmetrum();
+       public final static int product_altusmetrum = usb_product_altusmetrum();
+       public final static int product_telemetrum = usb_product_telemetrum();
+       public final static int product_teledongle = usb_product_teledongle();
+       public final static int product_teleterra = usb_product_teleterra();
+       public final static int product_altusmetrum_min = usb_product_altusmetrum_min();
+       public final static int product_altusmetrum_max = usb_product_altusmetrum_max();
+
+
+       public final static int product_any = 0x10000;
+       public final static int product_basestation = 0x10000 + 1;
+
+       public String toString() {
+               String  name = getName();
+               if (name == null)
+                       name = "Altus Metrum";
+               return String.format("%-20.20s %4d %s",
+                                    getName(), getSerial(), getPath());
+       }
+
+       public String toShortString() {
+               String  name = getName();
+               if (name == null)
+                       name = "Altus Metrum";
+               return String.format("%s %d %s",
+                                    name, getSerial(), getPath());
+
+       }
+
+       public boolean isAltusMetrum() {
+               if (getVendor() != vendor_altusmetrum)
+                       return false;
+               if (getProduct() < product_altusmetrum_min)
+                       return false;
+               if (getProduct() > product_altusmetrum_max)
+                       return false;
+               return true;
+       }
+
+       public boolean matchProduct(int want_product) {
+
+               if (!isAltusMetrum())
+                       return false;
+
+               if (want_product == product_any)
+                       return true;
+
+               if (want_product == product_basestation)
+                       return matchProduct(product_teledongle) || matchProduct(product_teleterra);
+
+               int have_product = getProduct();
+
+               if (have_product == product_altusmetrum)        /* old devices match any request */
+                       return true;
+
+               if (want_product == have_product)
+                       return true;
+
+               return false;
+       }
+
+       static AltosDevice[] list(int product) {
+               if (!load_library())
+                       return null;
+
+               SWIGTYPE_p_altos_list list = libaltos.altos_list_start();
+
+               ArrayList<AltosDevice> device_list = new ArrayList<AltosDevice>();
+               if (list != null) {
+                       SWIGTYPE_p_altos_file file;
+
+                       for (;;) {
+                               AltosDevice device = new AltosDevice();
+                               if (libaltos.altos_list_next(list, device) == 0)
+                                       break;
+                               if (device.matchProduct(product))
+                                       device_list.add(device);
+                       }
+                       libaltos.altos_list_finish(list);
+               }
+
+               AltosDevice[] devices = new AltosDevice[device_list.size()];
+               for (int i = 0; i < device_list.size(); i++)
+                       devices[i] = device_list.get(i);
+               return devices;
+       }
+}
\ No newline at end of file
diff --git a/altosui/AltosDeviceDialog.java b/altosui/AltosDeviceDialog.java
new file mode 100644 (file)
index 0000000..2966ad1
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * 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.libaltos;
+import libaltosJNI.altos_device;
+import libaltosJNI.SWIGTYPE_p_altos_file;
+import libaltosJNI.SWIGTYPE_p_altos_list;
+
+public class AltosDeviceDialog extends JDialog implements ActionListener {
+
+       private static AltosDeviceDialog        dialog;
+       private static AltosDevice              value = null;
+       private JList                           list;
+
+       public static AltosDevice show (Component frameComp, int product) {
+
+               Frame frame = JOptionPane.getFrameForComponent(frameComp);
+               AltosDevice[]   devices;
+               devices = AltosDevice.list(product);
+
+               if (devices != null && devices.length > 0) {
+                       value = null;
+                       dialog = new AltosDeviceDialog(frame, frameComp,
+                                                      devices,
+                                                      devices[0]);
+
+                       dialog.setVisible(true);
+                       return value;
+               } else {
+                       /* check for missing altos JNI library, which
+                        * will put up its own error dialog
+                        */
+                       if (AltosUI.load_library(frame)) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             "No AltOS devices available",
+                                                             "No AltOS devices",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+                       return null;
+               }
+       }
+
+       private AltosDeviceDialog (Frame frame, Component location,
+                                  AltosDevice[] devices,
+                                  AltosDevice initial) {
+               super(frame, "Device Selection", true);
+
+               value = null;
+
+               JButton cancelButton = new JButton("Cancel");
+               cancelButton.addActionListener(this);
+
+               final JButton selectButton = new JButton("Select");
+               selectButton.setActionCommand("select");
+               selectButton.addActionListener(this);
+               getRootPane().setDefaultButton(selectButton);
+
+               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) {
+                                                selectButton.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(cancelButton);
+               buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+               buttonPane.add(selectButton);
+
+               //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.
+               list.setSelectedValue(initial, true);
+               pack();
+               setLocationRelativeTo(location);
+       }
+
+       //Handle clicks on the Set and Cancel buttons.
+       public void actionPerformed(ActionEvent e) {
+               if ("select".equals(e.getActionCommand()))
+                       AltosDeviceDialog.value = (AltosDevice)(list.getSelectedValue());
+               AltosDeviceDialog.dialog.setVisible(false);
+       }
+
+}
diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java
new file mode 100644 (file)
index 0000000..3e71913
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosDisplayThread extends Thread {
+
+       Frame                   parent;
+       IdleThread              idle_thread;
+       AltosVoice              voice;
+       String                  name;
+       AltosFlightReader       reader;
+       int                     crc_errors;
+       AltosFlightDisplay      display;
+
+       synchronized void show(AltosState state, int crc_errors) {
+               if (state != null)
+                       display.show(state, crc_errors);
+       }
+
+       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 %d, bearing %s %d, elevation %d, range %d.\n",
+                                           (int) (state.height + 0.5),
+                        state.from_pad.bearing_words(
+                            AltosGreatCircle.BEARING_VOICE),
+                                           (int) (state.from_pad.bearing + 0.5),
+                                           (int) (state.elevation + 0.5),
+                                           (int) (state.range + 0.5));
+                       } else if (state.state > Altos.ao_flight_pad) {
+                               voice.speak("%d meters", (int) (state.height + 0.5));
+                       } 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 %d meters.",
+                                                   (int) (state.from_pad.bearing + 0.5),
+                                                   (int) (state.from_pad.distance + 0.5));
+                               ++reported_landing;
+                               if (state.state != Altos.ao_flight_landed) {
+                                       state.state = Altos.ao_flight_landed;
+                                       show(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: %d meters per second.",
+                                           (int) (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: %d meters.",
+                                           (int) (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();
+
+               display.reset();
+               try {
+                       for (;;) {
+                               try {
+                                       AltosRecord record = reader.read();
+                                       if (record == null)
+                                               break;
+                                       old_state = state;
+                                       state = new AltosState(record, state);
+                                       reader.update(state);
+                                       show(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(state, crc_errors);
+                               }
+                       }
+               } catch (InterruptedException ee) {
+                       interrupted = true;
+               } catch (IOException ie) {
+                       JOptionPane.showMessageDialog(parent,
+                                                     String.format("Error reading from \"%s\"", name),
+                                                     "Telemetry Read Error",
+                                                     JOptionPane.ERROR_MESSAGE);
+               } 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;
+       }
+}
diff --git a/altosui/AltosEepromDownload.java b/altosui/AltosEepromDownload.java
new file mode 100644 (file)
index 0000000..02fc36f
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * 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.*;
+
+public class AltosEepromDownload implements Runnable {
+
+       static final String[] state_names = {
+               "startup",
+               "idle",
+               "pad",
+               "boost",
+               "fast",
+               "coast",
+               "drogue",
+               "main",
+               "landed",
+               "invalid",
+       };
+
+       int[] ParseHex(String line) {
+               String[] tokens = line.split("\\s+");
+               int[] array = new int[tokens.length];
+
+               for (int i = 0; i < tokens.length; i++)
+                       try {
+                               array[i] = Integer.parseInt(tokens[i], 16);
+                       } catch (NumberFormatException ne) {
+                               return null;
+                       }
+               return array;
+       }
+
+       int checksum(int[] line) {
+               int     csum = 0x5a;
+               for (int i = 1; i < line.length; i++)
+                       csum += line[i];
+               return csum & 0xff;
+       }
+
+       void FlushPending(FileWriter file, LinkedList<String> pending) throws IOException {
+               while (!pending.isEmpty()) {
+                       file.write(pending.remove());
+               }
+       }
+
+       JFrame                  frame;
+       AltosDevice             device;
+       AltosSerial             serial_line;
+       boolean                 remote;
+       Thread                  eeprom_thread;
+       AltosEepromMonitor      monitor;
+
+       void CaptureLog() throws IOException, InterruptedException, TimeoutException {
+               int                     serial = 0;
+               int                     block, state_block = 0;
+               int                     addr;
+               int                     flight = 0;
+               int                     year = 0, month = 0, day = 0;
+               int                     state = 0;
+               boolean                 done = false;
+               boolean                 want_file = false;
+               boolean                 any_valid;
+               FileWriter              eeprom_file = null;
+               AltosFile               eeprom_name;
+               LinkedList<String>      eeprom_pending = new LinkedList<String>();
+
+               serial_line.printf("\nc s\nv\n");
+
+               /* Pull the serial number out of the version information */
+
+               for (;;) {
+                       String  line = serial_line.get_reply(5000);
+
+                       if (line == null)
+                               throw new TimeoutException();
+                       if (line.startsWith("serial-number")) {
+                               try {
+                                       serial = Integer.parseInt(line.substring(13).trim());
+                               } catch (NumberFormatException ne) {
+                                       serial = 0;
+                               }
+                       }
+
+                       eeprom_pending.add(String.format("%s\n", line));
+
+                       /* signals the end of the version info */
+                       if (line.startsWith("software-version"))
+                               break;
+               }
+               if (serial == 0)
+                       throw new IOException("no serial number found");
+
+               monitor.set_serial(serial);
+               /* Now scan the eeprom, reading blocks of data and converting to .eeprom file form */
+
+               state = 0; state_block = 0;
+               for (block = 0; !done && block < 511; block++) {
+                       serial_line.printf("e %x\n", block);
+                       any_valid = false;
+                       monitor.set_value(state_names[state], state, block - state_block);
+                       for (addr = 0; addr < 0x100;) {
+                               String  line = serial_line.get_reply(5000);
+                               if (line == null)
+                                       throw new TimeoutException();
+                               int[] values = ParseHex(line);
+
+                               if (values == null) {
+                                       System.out.printf("invalid line: %s\n", line);
+                                       continue;
+                               } else if (values[0] != addr) {
+                                       System.out.printf("data address out of sync at 0x%x\n",
+                                                         block * 256 + values[0]);
+                               } else if (checksum(values) != 0) {
+                                       System.out.printf("invalid checksum at 0x%x\n",
+                                                         block * 256 + values[0]);
+                               } else {
+                                       any_valid = true;
+                                       int     cmd = values[1];
+                                       int     tick = values[3] + (values[4] << 8);
+                                       int     a = values[5] + (values[6] << 8);
+                                       int     b = values[7] + (values[8] << 8);
+
+                                       if (cmd == Altos.AO_LOG_FLIGHT) {
+                                               flight = b;
+                                               monitor.set_flight(flight);
+                                       }
+
+                                       /* Monitor state transitions to update display */
+                                       if (cmd == Altos.AO_LOG_STATE && a <= Altos.ao_flight_landed) {
+                                               if (a > Altos.ao_flight_pad)
+                                                       want_file = true;
+                                               if (a > state)
+                                                       state_block = block;
+                                               state = a;
+                                       }
+
+                                       if (cmd == Altos.AO_LOG_GPS_DATE) {
+                                               year = 2000 + (a & 0xff);
+                                               month = (a >> 8) & 0xff;
+                                               day = (b & 0xff);
+                                               want_file = true;
+                                       }
+
+                                       if (eeprom_file == null) {
+                                               if (serial != 0 && flight != 0 && want_file) {
+                                                       if (year != 0 && month != 0 && day != 0)
+                                                               eeprom_name = new AltosFile(year, month, day, serial, flight, "eeprom");
+                                                       else
+                                                               eeprom_name = new AltosFile(serial, flight, "eeprom");
+
+                                                       monitor.set_file(eeprom_name.getName());
+                                                       eeprom_file = new FileWriter(eeprom_name);
+                                                       if (eeprom_file != null) {
+                                                               FlushPending(eeprom_file, eeprom_pending);
+                                                               eeprom_pending = null;
+                                                       }
+                                               }
+                                       }
+
+                                       String log_line = String.format("%c %4x %4x %4x\n",
+                                                                       cmd, tick, a, b);
+                                       if (eeprom_file != null)
+                                               eeprom_file.write(log_line);
+                                       else
+                                               eeprom_pending.add(log_line);
+
+                                       if (cmd == Altos.AO_LOG_STATE && a == Altos.ao_flight_landed) {
+                                               done = true;
+                                       }
+                               }
+                               addr += 8;
+                       }
+                       if (!any_valid)
+                               done = true;
+               }
+               if (eeprom_file == null) {
+                       eeprom_name = new AltosFile(serial,flight,"eeprom");
+                       eeprom_file = new FileWriter(eeprom_name);
+                       if (eeprom_file != null) {
+                               FlushPending(eeprom_file, eeprom_pending);
+                       }
+               }
+               if (eeprom_file != null) {
+                       eeprom_file.flush();
+                       eeprom_file.close();
+               }
+       }
+
+       public void run () {
+               if (remote) {
+                       serial_line.set_radio();
+                       serial_line.printf("p\nE 0\n");
+                       serial_line.flush_input();
+               }
+
+               monitor = new AltosEepromMonitor(frame, Altos.ao_flight_boost, Altos.ao_flight_landed);
+               monitor.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       eeprom_thread.interrupt();
+                               }
+                       });
+               try {
+                       CaptureLog();
+               } catch (IOException ee) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     device.toShortString(),
+                                                     ee.getLocalizedMessage(),
+                                                     JOptionPane.ERROR_MESSAGE);
+               } catch (InterruptedException ie) {
+               } catch (TimeoutException te) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     String.format("Connection to \"%s\" failed",
+                                                                   device.toShortString()),
+                                                     "Connection Failed",
+                                                     JOptionPane.ERROR_MESSAGE);
+               }
+               if (remote)
+                       serial_line.printf("~");
+               monitor.done();
+               serial_line.flush_output();
+               serial_line.close();
+       }
+
+       public AltosEepromDownload(JFrame given_frame) {
+               frame = given_frame;
+               device = AltosDeviceDialog.show(frame, AltosDevice.product_any);
+
+               remote = false;
+
+               if (device != null) {
+                       try {
+                               serial_line = new AltosSerial(device);
+                               if (!device.matchProduct(AltosDevice.product_telemetrum))
+                                       remote = true;
+                               eeprom_thread = new Thread(this);
+                               eeprom_thread.start();
+                       } catch (FileNotFoundException ee) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             String.format("Cannot open device \"%s\"",
+                                                                           device.toShortString()),
+                                                             "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);
+                       } catch (IOException ee) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             device.toShortString(),
+                                                             ee.getLocalizedMessage(),
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+       }
+}
diff --git a/altosui/AltosEepromIterable.java b/altosui/AltosEepromIterable.java
new file mode 100644 (file)
index 0000000..f8e6d7e
--- /dev/null
@@ -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 java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/*
+ * AltosRecords with an index field so they can be sorted by tick while preserving
+ * the original ordering for elements with matching ticks
+ */
+class AltosOrderedRecord extends AltosEepromRecord implements Comparable<AltosOrderedRecord> {
+
+       public int      index;
+
+       public AltosOrderedRecord(String line, int in_index, int prev_tick, boolean prev_tick_valid)
+               throws ParseException {
+               super(line);
+               if (prev_tick_valid) {
+                       tick |= (prev_tick & ~0xffff);
+                       if (tick < prev_tick) {
+                               if (prev_tick - tick > 0x8000)
+                                       tick += 0x10000;
+                       } else {
+                               if (tick - prev_tick > 0x8000)
+                                       tick -= 0x10000;
+                       }
+               }
+               index = in_index;
+       }
+
+       public AltosOrderedRecord(int in_cmd, int in_tick, int in_a, int in_b, int in_index) {
+               super(in_cmd, in_tick, in_a, in_b);
+               index = in_index;
+       }
+
+       public int compareTo(AltosOrderedRecord o) {
+               int     tick_diff = tick - o.tick;
+               if (tick_diff != 0)
+                       return tick_diff;
+               return index - o.index;
+       }
+}
+
+public class AltosEepromIterable extends AltosRecordIterable {
+
+       static final int        seen_flight = 1;
+       static final int        seen_sensor = 2;
+       static final int        seen_temp_volt = 4;
+       static final int        seen_deploy = 8;
+       static final int        seen_gps_time = 16;
+       static final int        seen_gps_lat = 32;
+       static final int        seen_gps_lon = 64;
+
+       static final int        seen_basic = seen_flight|seen_sensor|seen_temp_volt|seen_deploy;
+
+       AltosEepromRecord       flight_record;
+       AltosEepromRecord       gps_date_record;
+
+       TreeSet<AltosOrderedRecord>     records;
+
+       LinkedList<AltosRecord> list;
+
+       class EepromState {
+               int     seen;
+               int     n_pad_samples;
+               double  ground_pres;
+               int     gps_tick;
+               int     boost_tick;
+
+               EepromState() {
+                       seen = 0;
+                       n_pad_samples = 0;
+                       ground_pres = 0.0;
+                       gps_tick = 0;
+               }
+       }
+
+       void update_state(AltosRecord state, AltosEepromRecord record, EepromState eeprom) {
+               state.tick = record.tick;
+               switch (record.cmd) {
+               case Altos.AO_LOG_FLIGHT:
+                       eeprom.seen |= seen_flight;
+                       state.ground_accel = record.a;
+                       state.flight_accel = record.a;
+                       state.flight = record.b;
+                       eeprom.boost_tick = record.tick;
+                       break;
+               case Altos.AO_LOG_SENSOR:
+                       state.accel = record.a;
+                       state.pres = record.b;
+                       if (state.state < Altos.ao_flight_boost) {
+                               eeprom.n_pad_samples++;
+                               eeprom.ground_pres += state.pres;
+                               state.ground_pres = (int) (eeprom.ground_pres / eeprom.n_pad_samples);
+                               state.flight_pres = state.ground_pres;
+                       } else {
+                               state.flight_pres = (state.flight_pres * 15 + state.pres) / 16;
+                               state.flight_accel = (state.flight_accel * 15 + state.accel) / 16;
+                               state.flight_vel += (state.accel_plus_g - state.accel);
+                       }
+                       eeprom.seen |= seen_sensor;
+                       break;
+               case Altos.AO_LOG_TEMP_VOLT:
+                       state.temp = record.a;
+                       state.batt = record.b;
+                       eeprom.seen |= seen_temp_volt;
+                       break;
+               case Altos.AO_LOG_DEPLOY:
+                       state.drogue = record.a;
+                       state.main = record.b;
+                       eeprom.seen |= seen_deploy;
+                       break;
+               case Altos.AO_LOG_STATE:
+                       state.state = record.a;
+                       break;
+               case Altos.AO_LOG_GPS_TIME:
+                       eeprom.gps_tick = state.tick;
+                       AltosGPS old = state.gps;
+                       state.gps = new AltosGPS();
+
+                       /* GPS date doesn't get repeated through the file */
+                       if (old != null) {
+                               state.gps.year = old.year;
+                               state.gps.month = old.month;
+                               state.gps.day = old.day;
+                       }
+                       state.gps.hour = (record.a & 0xff);
+                       state.gps.minute = (record.a >> 8);
+                       state.gps.second = (record.b & 0xff);
+
+                       int flags = (record.b >> 8);
+                       state.gps.connected = (flags & Altos.AO_GPS_RUNNING) != 0;
+                       state.gps.locked = (flags & Altos.AO_GPS_VALID) != 0;
+                       state.gps.date_valid = (flags & Altos.AO_GPS_DATE_VALID) != 0;
+                       state.gps.nsat = (flags & Altos.AO_GPS_NUM_SAT_MASK) >>
+                               Altos.AO_GPS_NUM_SAT_SHIFT;
+                       break;
+               case Altos.AO_LOG_GPS_LAT:
+                       int lat32 = record.a | (record.b << 16);
+                       state.gps.lat = (double) lat32 / 1e7;
+                       break;
+               case Altos.AO_LOG_GPS_LON:
+                       int lon32 = record.a | (record.b << 16);
+                       state.gps.lon = (double) lon32 / 1e7;
+                       break;
+               case Altos.AO_LOG_GPS_ALT:
+                       state.gps.alt = record.a;
+                       break;
+               case Altos.AO_LOG_GPS_SAT:
+                       if (state.tick == eeprom.gps_tick) {
+                               int svid = record.a;
+                               int c_n0 = record.b >> 8;
+                               state.gps.add_sat(svid, c_n0);
+                       }
+                       break;
+               case Altos.AO_LOG_GPS_DATE:
+                       state.gps.year = (record.a & 0xff) + 2000;
+                       state.gps.month = record.a >> 8;
+                       state.gps.day = record.b & 0xff;
+                       break;
+
+               case Altos.AO_LOG_CONFIG_VERSION:
+                       break;
+               case Altos.AO_LOG_MAIN_DEPLOY:
+                       break;
+               case Altos.AO_LOG_APOGEE_DELAY:
+                       break;
+               case Altos.AO_LOG_RADIO_CHANNEL:
+                       break;
+               case Altos.AO_LOG_CALLSIGN:
+                       state.callsign = record.data;
+                       break;
+               case Altos.AO_LOG_ACCEL_CAL:
+                       state.accel_plus_g = record.a;
+                       state.accel_minus_g = record.b;
+                       break;
+               case Altos.AO_LOG_RADIO_CAL:
+                       break;
+               case Altos.AO_LOG_MANUFACTURER:
+                       break;
+               case Altos.AO_LOG_PRODUCT:
+                       break;
+               case Altos.AO_LOG_SERIAL_NUMBER:
+                       state.serial = record.a;
+                       break;
+               case Altos.AO_LOG_SOFTWARE_VERSION:
+                       break;
+               }
+       }
+
+       LinkedList<AltosRecord> make_list() {
+               LinkedList<AltosRecord>         list = new LinkedList<AltosRecord>();
+               Iterator<AltosOrderedRecord>    iterator = records.iterator();
+               AltosOrderedRecord              record = null;
+               AltosRecord                     state = new AltosRecord();
+               boolean                         last_reported = false;
+               EepromState                     eeprom = new EepromState();
+
+               state.state = Altos.ao_flight_pad;
+               state.accel_plus_g = 15758;
+               state.accel_minus_g = 16294;
+
+               /* Pull in static data from the flight and gps_date records */
+               if (flight_record != null)
+                       update_state(state, flight_record, eeprom);
+               if (gps_date_record != null)
+                       update_state(state, gps_date_record, eeprom);
+
+               while (iterator.hasNext()) {
+                       record = iterator.next();
+                       if ((eeprom.seen & seen_basic) == seen_basic && record.tick != state.tick) {
+                               AltosRecord r = new AltosRecord(state);
+                               r.time = (r.tick - eeprom.boost_tick) / 100.0;
+                               list.add(r);
+                       }
+                       update_state(state, record, eeprom);
+               }
+               AltosRecord r = new AltosRecord(state);
+               r.time = (r.tick - eeprom.boost_tick) / 100.0;
+               list.add(r);
+               return list;
+       }
+
+       public Iterator<AltosRecord> iterator() {
+               if (list == null)
+                       list = make_list();
+               return list.iterator();
+       }
+
+       public void write_comments(PrintStream out) {
+               Iterator<AltosOrderedRecord>    iterator = records.iterator();
+               out.printf("# Comments\n");
+               while (iterator.hasNext()) {
+                       AltosOrderedRecord      record = iterator.next();
+                       switch (record.cmd) {
+                       case Altos.AO_LOG_CONFIG_VERSION:
+                               out.printf("# Config version: %s\n", record.data);
+                               break;
+                       case Altos.AO_LOG_MAIN_DEPLOY:
+                               out.printf("# Main deploy: %s\n", record.a);
+                               break;
+                       case Altos.AO_LOG_APOGEE_DELAY:
+                               out.printf("# Apogee delay: %s\n", record.a);
+                               break;
+                       case Altos.AO_LOG_RADIO_CHANNEL:
+                               out.printf("# Radio channel: %s\n", record.a);
+                               break;
+                       case Altos.AO_LOG_CALLSIGN:
+                               out.printf("# Callsign: %s\n", record.data);
+                               break;
+                       case Altos.AO_LOG_ACCEL_CAL:
+                               out.printf ("# Accel cal: %d %d\n", record.a, record.b);
+                               break;
+                       case Altos.AO_LOG_RADIO_CAL:
+                               out.printf ("# Radio cal: %d\n", record.a);
+                               break;
+                       case Altos.AO_LOG_MANUFACTURER:
+                               out.printf ("# Manufacturer: %s\n", record.data);
+                               break;
+                       case Altos.AO_LOG_PRODUCT:
+                               out.printf ("# Product: %s\n", record.data);
+                               break;
+                       case Altos.AO_LOG_SERIAL_NUMBER:
+                               out.printf ("# Serial number: %d\n", record.a);
+                               break;
+                       case Altos.AO_LOG_SOFTWARE_VERSION:
+                               out.printf ("# Software version: %s\n", record.data);
+                               break;
+                       }
+               }
+       }
+
+       /*
+        * Given an AO_LOG_GPS_TIME record with correct time, and one
+        * missing time, rewrite the missing time values with the good
+        * ones, assuming that the difference between them is 'diff' seconds
+        */
+       void update_time(AltosOrderedRecord good, AltosOrderedRecord bad) {
+
+               int diff = (bad.tick - good.tick + 50) / 100;
+
+               int hour = (good.a & 0xff);
+               int minute = (good.a >> 8);
+               int second = (good.b & 0xff);
+               int flags = (good.b >> 8);
+               int seconds = hour * 3600 + minute * 60 + second;
+
+               /* Make sure this looks like a good GPS value */
+               if ((flags & Altos.AO_GPS_NUM_SAT_MASK) >> Altos.AO_GPS_NUM_SAT_SHIFT < 4)
+                       flags = (flags & ~Altos.AO_GPS_NUM_SAT_MASK) | (4 << Altos.AO_GPS_NUM_SAT_SHIFT);
+               flags |= Altos.AO_GPS_RUNNING;
+               flags |= Altos.AO_GPS_VALID;
+
+               int new_seconds = seconds + diff;
+               if (new_seconds < 0)
+                       new_seconds += 24 * 3600;
+               int new_second = (new_seconds % 60);
+               int new_minutes = (new_seconds / 60);
+               int new_minute = (new_minutes % 60);
+               int new_hours = (new_minutes / 60);
+               int new_hour = (new_hours % 24);
+
+               bad.a = new_hour + (new_minute << 8);
+               bad.b = new_second + (flags << 8);
+       }
+
+       /*
+        * Read the whole file, dumping records into a RB tree so
+        * we can enumerate them in time order -- the eeprom data
+        * are sometimes out of order with GPS data getting timestamps
+        * matching the first packet out of the GPS unit but not
+        * written until the final GPS packet has been received.
+        */
+       public AltosEepromIterable (FileInputStream input) {
+               records = new TreeSet<AltosOrderedRecord>();
+
+               AltosOrderedRecord last_gps_time = null;
+
+               int index = 0;
+               int prev_tick = 0;
+               boolean prev_tick_valid = false;
+               boolean missing_time = false;
+
+               try {
+                       for (;;) {
+                               String line = AltosRecord.gets(input);
+                               if (line == null)
+                                       break;
+                               AltosOrderedRecord record = new AltosOrderedRecord(line, index++, prev_tick, prev_tick_valid);
+                               if (record == null)
+                                       break;
+                               if (record.cmd == Altos.AO_LOG_INVALID)
+                                       continue;
+                               prev_tick = record.tick;
+                               if (record.cmd < Altos.AO_LOG_CONFIG_VERSION)
+                                       prev_tick_valid = true;
+                               if (record.cmd == Altos.AO_LOG_FLIGHT) {
+                                       flight_record = record;
+                                       continue;
+                               }
+
+                               /* Two firmware bugs caused the loss of some GPS data.
+                                * The flight date would never be recorded, and often
+                                * the flight time would get overwritten by another
+                                * record. Detect the loss of the GPS date and fix up the
+                                * missing time records
+                                */
+                               if (record.cmd == Altos.AO_LOG_GPS_DATE) {
+                                       gps_date_record = record;
+                                       continue;
+                               }
+
+                               /* go back and fix up any missing time values */
+                               if (record.cmd == Altos.AO_LOG_GPS_TIME) {
+                                       last_gps_time = record;
+                                       if (missing_time) {
+                                               Iterator<AltosOrderedRecord> iterator = records.iterator();
+                                               while (iterator.hasNext()) {
+                                                       AltosOrderedRecord old = iterator.next();
+                                                       if (old.cmd == Altos.AO_LOG_GPS_TIME &&
+                                                           old.a == -1 && old.b == -1)
+                                                       {
+                                                               update_time(record, old);
+                                                       }
+                                               }
+                                               missing_time = false;
+                                       }
+                               }
+
+                               if (record.cmd == Altos.AO_LOG_GPS_LAT) {
+                                       if (last_gps_time == null || last_gps_time.tick != record.tick) {
+                                               AltosOrderedRecord add_gps_time = new AltosOrderedRecord(Altos.AO_LOG_GPS_TIME,
+                                                                                                        record.tick,
+                                                                                                        -1, -1, index-1);
+                                               if (last_gps_time != null)
+                                                       update_time(last_gps_time, add_gps_time);
+                                               else
+                                                       missing_time = true;
+
+                                               records.add(add_gps_time);
+                                               record.index = index++;
+                                       }
+                               }
+                               records.add(record);
+
+                               /* Bail after reading the 'landed' record; we're all done */
+                               if (record.cmd == Altos.AO_LOG_STATE &&
+                                   record.a == Altos.ao_flight_landed)
+                                       break;
+                       }
+               } catch (IOException io) {
+               } catch (ParseException pe) {
+               }
+               try {
+                       input.close();
+               } catch (IOException ie) {
+               }
+       }
+}
diff --git a/altosui/AltosEepromMonitor.java b/altosui/AltosEepromMonitor.java
new file mode 100644 (file)
index 0000000..7ff00ea
--- /dev/null
@@ -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.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosEepromMonitor extends JDialog {
+
+       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);
+       }
+
+       public void set_value(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_serial(int serial) {
+               serial_value.setText(String.format("%d", serial));
+       }
+
+       public void set_flight(int flight) {
+               flight_value.setText(String.format("%d", flight));
+       }
+
+       public void set_file(String file) {
+               file_value.setText(String.format("%s", file));
+       }
+
+       public void done() {
+               setVisible(false);
+               dispose();
+       }
+}
diff --git a/altosui/AltosEepromRecord.java b/altosui/AltosEepromRecord.java
new file mode 100644 (file)
index 0000000..5a67381
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosEepromRecord {
+       public int      cmd;
+       public int      tick;
+       public int      a;
+       public int      b;
+       public String   data;
+       public boolean  tick_valid;
+
+       public AltosEepromRecord (String line) {
+               tick_valid = false;
+               tick = 0;
+               a = 0;
+               b = 0;
+               data = null;
+               if (line == null) {
+                       cmd = Altos.AO_LOG_INVALID;
+                       data = "";
+               } else {
+                       try {
+                               String[] tokens = line.split("\\s+");
+
+                               if (tokens[0].length() == 1) {
+                                       if (tokens.length != 4) {
+                                               cmd = Altos.AO_LOG_INVALID;
+                                               data = line;
+                                       } else {
+                                               cmd = tokens[0].codePointAt(0);
+                                               tick = Integer.parseInt(tokens[1],16);
+                                               tick_valid = true;
+                                               a = Integer.parseInt(tokens[2],16);
+                                               b = Integer.parseInt(tokens[3],16);
+                                       }
+                               } else if (tokens[0].equals("Config") && tokens[1].equals("version:")) {
+                                       cmd = Altos.AO_LOG_CONFIG_VERSION;
+                                       data = tokens[2];
+                               } else if (tokens[0].equals("Main") && tokens[1].equals("deploy:")) {
+                                       cmd = Altos.AO_LOG_MAIN_DEPLOY;
+                                       a = Integer.parseInt(tokens[2]);
+                               } else if (tokens[0].equals("Apogee") && tokens[1].equals("delay:")) {
+                                       cmd = Altos.AO_LOG_APOGEE_DELAY;
+                                       a = Integer.parseInt(tokens[2]);
+                               } else if (tokens[0].equals("Radio") && tokens[1].equals("channel:")) {
+                                       cmd = Altos.AO_LOG_RADIO_CHANNEL;
+                                       a = Integer.parseInt(tokens[2]);
+                               } else if (tokens[0].equals("Callsign:")) {
+                                       cmd = Altos.AO_LOG_CALLSIGN;
+                                       data = tokens[1].replaceAll("\"","");
+                               } else if (tokens[0].equals("Accel") && tokens[1].equals("cal")) {
+                                       cmd = Altos.AO_LOG_ACCEL_CAL;
+                                       a = Integer.parseInt(tokens[3]);
+                                       b = Integer.parseInt(tokens[5]);
+                               } else if (tokens[0].equals("Radio") && tokens[1].equals("cal:")) {
+                                       cmd = Altos.AO_LOG_RADIO_CAL;
+                                       a = Integer.parseInt(tokens[2]);
+                               } else if (tokens[0].equals("manufacturer")) {
+                                       cmd = Altos.AO_LOG_MANUFACTURER;
+                                       data = tokens[1];
+                               } else if (tokens[0].equals("product")) {
+                                       cmd = Altos.AO_LOG_PRODUCT;
+                                       data = tokens[1];
+                               } else if (tokens[0].equals("serial-number")) {
+                                       cmd = Altos.AO_LOG_SERIAL_NUMBER;
+                                       a = Integer.parseInt(tokens[1]);
+                               } else if (tokens[0].equals("software-version")) {
+                                       cmd = Altos.AO_LOG_SOFTWARE_VERSION;
+                                       data = tokens[1];
+                               } else {
+                                       cmd = Altos.AO_LOG_INVALID;
+                                       data = line;
+                               }
+                       } catch (NumberFormatException ne) {
+                               cmd = Altos.AO_LOG_INVALID;
+                               data = line;
+                       }
+               }
+       }
+
+       public AltosEepromRecord(int in_cmd, int in_tick, int in_a, int in_b) {
+               tick_valid = true;
+               cmd = in_cmd;
+               tick = in_tick;
+               a = in_a;
+               b = in_b;
+       }
+}
diff --git a/altosui/AltosFile.java b/altosui/AltosFile.java
new file mode 100644 (file)
index 0000000..0636057
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.File;
+import java.util.*;
+
+class AltosFile extends File {
+
+       public AltosFile(int year, int month, int day, int serial, int flight, String extension) {
+               super (AltosPreferences.logdir(),
+                      String.format("%04d-%02d-%02d-serial-%03d-flight-%03d.%s",
+                                    year, month, day, serial, flight, extension));
+       }
+
+       public AltosFile(int serial, int flight, String extension) {
+               this(Calendar.getInstance().get(Calendar.YEAR),
+                    Calendar.getInstance().get(Calendar.MONTH) + 1,
+                    Calendar.getInstance().get(Calendar.DAY_OF_MONTH),
+                    serial,
+                    flight,
+                    extension);
+       }
+
+       public AltosFile(AltosTelemetry telem) {
+               this(telem.serial, telem.flight, "telem");
+       }
+}
diff --git a/altosui/AltosFlash.java b/altosui/AltosFlash.java
new file mode 100644 (file)
index 0000000..3af25c2
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class 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 {
+               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 s, int percent) {
+               if (listener != null && !aborted)
+                       listener.actionPerformed(new ActionEvent(this,
+                                                                percent,
+                                                                s));
+       }
+
+       void action(int part, int total) {
+               int percent = 100 * part / total;
+               action(String.format("%d/%d (%d%%)",
+                                    part, total, percent),
+                      percent);
+       }
+
+       void 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() throws IOException, FileNotFoundException, InterruptedException {
+               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;
+
+                       /* 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);
+
+                       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]));
+                       remain -= this_time;
+                       flash_addr += this_time;
+                       image_start += this_time;
+
+                       action(image.data.length - remain, image.data.length);
+               }
+               if (!aborted) {
+                       action("done", 100);
+                       debug.set_pc(image.address);
+                       debug.resume();
+               }
+               debug.close();
+       }
+
+       public void abort() {
+               aborted = true;
+               debug.close();
+       }
+
+       public void addActionListener(ActionListener l) {
+               listener = l;
+       }
+
+       public boolean check_rom_config() {
+               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;
+               debug = new AltosDebug(in_debug_dongle);
+               input = new FileInputStream(file);
+               image = new AltosHexfile(input);
+               if (!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 (file)
index 0000000..f63097a
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosFlashUI
+       extends JDialog
+       implements Runnable, ActionListener
+{
+       Container       pane;
+       Box             box;
+       JLabel          serial_label;
+       JLabel          serial_value;
+       JLabel          file_label;
+       JLabel          file_value;
+       JProgressBar    pbar;
+       JButton         cancel;
+
+       File            file;
+       Thread          thread;
+       JFrame          frame;
+       AltosDevice     debug_dongle;
+       AltosFlash      flash;
+
+       public void actionPerformed(ActionEvent e) {
+               if (e.getSource() == cancel) {
+                       abort();
+                       dispose();
+               } else {
+                       String  cmd = e.getActionCommand();
+                       if (cmd.equals("done"))
+                               ;
+                       else if (cmd.equals("start")) {
+                               setVisible(true);
+                       } else {
+                               pbar.setValue(e.getID());
+                               pbar.setString(cmd);
+                       }
+               }
+       }
+
+       public void run() {
+               try {
+                       flash = new AltosFlash(file, debug_dongle);
+                       flash.addActionListener(this);
+                       AltosRomconfigUI romconfig_ui = new AltosRomconfigUI (frame);
+
+                       romconfig_ui.set(flash.romconfig());
+                       AltosRomconfig romconfig = romconfig_ui.showDialog();
+
+                       if (romconfig != null && romconfig.valid()) {
+                               flash.set_romconfig(romconfig);
+                               serial_value.setText(String.format("%d",
+                                                                  flash.romconfig().serial_number));
+                               file_value.setText(file.toString());
+                               setVisible(true);
+                               flash.flash();
+                               flash = null;
+                       }
+               } catch (FileNotFoundException ee) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     "Cannot open image",
+                                                     file.toString(),
+                                                     JOptionPane.ERROR_MESSAGE);
+               } catch (AltosSerialInUseException si) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     String.format("Device \"%s\" already in use",
+                                                                   debug_dongle.toShortString()),
+                                                     "Device in use",
+                                                     JOptionPane.ERROR_MESSAGE);
+               } catch (IOException e) {
+                       JOptionPane.showMessageDialog(frame,
+                                                     e.getMessage(),
+                                                     file.toString(),
+                                                     JOptionPane.ERROR_MESSAGE);
+               } catch (InterruptedException ie) {
+               } finally {
+                       abort();
+               }
+               dispose();
+       }
+
+       public void abort() {
+               if (flash != null)
+                       flash.abort();
+       }
+
+       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("");
+               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);
+       }
+
+       public AltosFlashUI(JFrame in_frame) {
+               super(in_frame, "Program Altusmetrum Device", false);
+
+               frame = in_frame;
+
+               build_dialog();
+
+               debug_dongle = AltosDeviceDialog.show(frame, AltosDevice.product_any);
+
+               if (debug_dongle == null)
+                       return;
+
+               JFileChooser    hexfile_chooser = new JFileChooser();
+
+               File firmwaredir = AltosPreferences.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;
+
+               file = hexfile_chooser.getSelectedFile();
+
+               if (file != null)
+                       AltosPreferences.set_firmwaredir(file.getParentFile());
+
+               thread = new Thread(this);
+               thread.start();
+       }
+}
\ No newline at end of file
diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java
new file mode 100644 (file)
index 0000000..d18d1d1
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+public interface AltosFlightDisplay {
+       void reset();
+
+       void show(AltosState state, int crc_errors);
+}
diff --git a/altosui/AltosFlightInfoTableModel.java b/altosui/AltosFlightInfoTableModel.java
new file mode 100644 (file)
index 0000000..e23eff6
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosFlightInfoTableModel extends AbstractTableModel {
+       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/AltosFlightReader.java b/altosui/AltosFlightReader.java
new file mode 100644 (file)
index 0000000..3d59de9
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.text.*;
+import java.io.*;
+
+public class AltosFlightReader {
+       String name;
+
+       int serial;
+
+       void init() { }
+
+       AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; }
+
+       void close(boolean interrupted) { }
+
+       void set_channel(int channel) { }
+
+       void update(AltosState state) throws InterruptedException { }
+}
diff --git a/altosui/AltosFlightStatus.java b/altosui/AltosFlightStatus.java
new file mode 100644 (file)
index 0000000..59c9e9d
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class 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("");
+               }
+               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 (dBm)");
+               }
+       }
+
+       RSSI rssi;
+
+       public void reset () {
+               call.reset();
+               serial.reset();
+               flight.reset();
+               flight_state.reset();
+               rssi.reset();
+       }
+
+       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);
+       }
+
+       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);
+       }
+}
diff --git a/altosui/AltosFlightStatusTableModel.java b/altosui/AltosFlightStatusTableModel.java
new file mode 100644 (file)
index 0000000..4c24b6a
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosFlightStatusTableModel extends AbstractTableModel {
+       private String[] columnNames = {"Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
+       private Object[] data = { 0, "idle", 0, 0 };
+
+       public int getColumnCount() { return columnNames.length; }
+       public int getRowCount() { return 2; }
+       public Object getValueAt(int row, int col) {
+               if (row == 0)
+                       return columnNames[col];
+               return data[col];
+       }
+
+       public void setValueAt(Object value, int col) {
+               data[col] = value;
+               fireTableCellUpdated(1, col);
+       }
+
+       public void setValueAt(Object value, int row, int col) {
+               setValueAt(value, col);
+       }
+
+       public void set(AltosState state) {
+               setValueAt(String.format("%1.0f", state.height), 0);
+               setValueAt(state.data.state(), 1);
+               setValueAt(state.data.rssi, 2);
+               double speed = state.baro_speed;
+               if (state.ascent)
+                       speed = state.speed;
+               setValueAt(String.format("%1.0f", speed), 3);
+       }
+}
diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java
new file mode 100644 (file)
index 0000000..24d25bd
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package altosui;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosFlightUI extends JFrame implements AltosFlightDisplay {
+       String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
+       Object[][] statusData = { { "0", "pad", "-50", "0" } };
+
+       AltosVoice              voice;
+       AltosFlightReader       reader;
+       AltosDisplayThread      thread;
+
+       JTabbedPane     pane;
+
+       AltosPad        pad;
+       AltosAscent     ascent;
+       AltosDescent    descent;
+       AltosLanded     landed;
+       AltosSiteMap    sitemap;
+
+       private AltosFlightStatus flightStatus;
+       private AltosInfoTable flightInfo;
+
+       static final int tab_pad = 1;
+       static final int tab_ascent = 2;
+       static final int tab_descent = 3;
+       static final int tab_landed = 4;
+
+       int cur_tab = 0;
+
+       boolean exit_on_close = false;
+
+       int which_tab(AltosState state) {
+               if (state.state < Altos.ao_flight_boost)
+                       return tab_pad;
+               if (state.state <= Altos.ao_flight_coast)
+                       return tab_ascent;
+               if (state.state <= Altos.ao_flight_main)
+                       return tab_descent;
+               return tab_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 show(AltosState state, int crc_errors) {
+               int     tab = which_tab(state);
+               pad.show(state, crc_errors);
+               ascent.show(state, crc_errors);
+               descent.show(state, crc_errors);
+               landed.show(state, crc_errors);
+               if (tab != cur_tab) {
+                       switch (tab) {
+                       case tab_pad:
+                               pane.setSelectedComponent(pad);
+                               break;
+                       case tab_ascent:
+                               pane.setSelectedComponent(ascent);
+                               break;
+                       case tab_descent:
+                               pane.setSelectedComponent(descent);
+                               break;
+                       case tab_landed:
+                               pane.setSelectedComponent(landed);
+                       }
+                       cur_tab = tab;
+               }
+               flightStatus.show(state, crc_errors);
+               flightInfo.show(state, crc_errors);
+               sitemap.show(state, crc_errors);
+       }
+
+       public void set_exit_on_close() {
+               exit_on_close = true;
+       }
+
+       Container       bag;
+       JComboBox       channels;
+
+       public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) {
+               AltosPreferences.init(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
+                       channels = new AltosChannelMenu(AltosPreferences.channel(serial));
+                       channels.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       int channel = channels.getSelectedIndex();
+                                       reader.set_channel(channel);
+                               }
+                       });
+                       c.gridx = 0;
+                       c.gridy = 0;
+                       c.anchor = GridBagConstraints.WEST;
+                       bag.add (channels, c);
+               }
+
+               /* Flight status is always visible */
+               flightStatus = new AltosFlightStatus();
+               c.gridx = 0;
+               c.gridy = 1;
+               c.fill = GridBagConstraints.HORIZONTAL;
+               c.weightx = 1;
+               bag.add(flightStatus, c);
+
+               /* 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();
+               pane.add("Landed", landed);
+
+               flightInfo = new AltosInfoTable();
+               pane.add("Table", new JScrollPane(flightInfo));
+
+               sitemap = new AltosSiteMap();
+               pane.add("Site Map", sitemap);
+
+               /* Make the tabbed pane use the rest of the window space */