--- /dev/null
+windows/
+linux/
+macosx/
+fat/
+Manifest.txt
+Manifest-fat.txt
+AltosVersion.java
+Info.plist
+libaltosJNI
+classes
+telegps
+telegps-test
+telegps-jdb
+classtelegps.stamp
+telegps-windows.nsi
+TeleGPS-Linux-*.tar.bz2
+TeleGPS-Linux-*.sh
+TeleGPS-Mac-*.zip
+TeleGPS-Windows-*.exe
+*.desktop
+telegps-windows.log
+*.dll
+*.dylib
+*.so
+*.jar
+*.class
+*.dmg
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleName</key>
+ <string>TestStand</string>
+ <key>CFBundleVersion</key>
+ <string>@VERSION@</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <string>true</string>
+ <key>CFBundleExecutable</key>
+ <string>JavaApplicationStub</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.altusmetrum.teststand</string>
+ <key>CFBundleSignature</key>
+ <string>Altu</string>
+ <key>CFBundleGetInfoString</key>
+ <string>TestStand version @VERSION@</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleIconFile</key>
+ <string>altusmetrum-teststand.icns</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeName</key>
+ <string>Eeprom</string>
+ <key>CFBundleTypeIconFile</key>
+ <string>application-vnd.altusmetrum.eeprom.icns</string>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>eeprom</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ </dict>
+ </array>
+ <key>Java</key>
+ <dict>
+ <key>MainClass</key>
+ <string>org.altusmetrum.teststand.TestStand</string>
+ <key>JVMVersion</key>
+ <string>1.5+</string>
+ <key>ClassPath</key>
+ <array>
+ <string>$JAVAROOT/teststand.jar</string>
+ <string>$JAVAROOT/freetts.jar</string>
+ </array>
+ <key>VMOptions</key>
+ <array>
+ <string>-Xms512M</string>
+ <string>-Xmx512M</string>
+ <string>-Dosgi.clean=true</string>
+ </array>
+ </dict>
+</dict>
+</plist>
--- /dev/null
+JAVAROOT=classes
+AM_JAVACFLAGS=-target 1.6 -encoding UTF-8 -Xlint:deprecation -Xlint:unchecked -source 6
+
+man_MANS=teststand.1
+
+altoslibdir=$(libdir)/altos
+
+CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:../altoslib/*:../altosuilib/*:../libaltos:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar:$(FREETTS)/freetts.jar"
+
+bin_SCRIPTS=teststand
+
+teststanddir=$(datadir)/java
+
+teststand_JAVA= \
+ TestStand.java \
+ TestStandStatus.java \
+ TestStandStatusUpdate.java \
+ TestStandInfo.java \
+ TestStandState.java \
+ TestStandConfig.java \
+ TestStandConfigUI.java \
+ TestStandPreferences.java \
+ TestStandGraphUI.java \
+ TestStandDisplayThread.java
+
+JFREECHART_CLASS= \
+ jfreechart.jar
+
+JCOMMON_CLASS=\
+ jcommon.jar
+
+FREETTS_CLASS= \
+ cmudict04.jar \
+ cmulex.jar \
+ cmu_time_awb.jar \
+ cmutimelex.jar \
+ cmu_us_kal.jar \
+ en_us.jar \
+ freetts.jar
+
+JAR=teststand.jar
+
+FATJAR=teststand-fat.jar
+
+if MULTI_ARCH
+LIBALTOS_LINUX=libaltos32.so libaltos64.so
+else
+LIBALTOS_LINUX=libaltos.so
+endif
+
+LIBALTOS= \
+ $(LIBALTOS_LINUX) \
+ libaltos.dylib \
+ altos64.dll \
+ altos.dll
+
+ALTOSLIB_CLASS=\
+ altoslib_$(ALTOSLIB_VERSION).jar
+
+ALTOSUILIB_CLASS=\
+ altosuilib_$(ALTOSUILIB_VERSION).jar
+
+# Icons
+ICONDIR=$(top_srcdir)/icon
+
+JAVA_ICONS=\
+ $(ICONDIR)/altusmetrum-teststand-16.png \
+ $(ICONDIR)/altusmetrum-teststand-32.png \
+ $(ICONDIR)/altusmetrum-teststand-48.png \
+ $(ICONDIR)/altusmetrum-teststand-64.png \
+ $(ICONDIR)/altusmetrum-teststand-128.png\
+ $(ICONDIR)/altusmetrum-teststand-256.png
+
+# icon base names for jar
+ICONJAR= \
+ -C $(ICONDIR) altusmetrum-teststand-16.png \
+ -C $(ICONDIR) altusmetrum-teststand-32.png \
+ -C $(ICONDIR) altusmetrum-teststand-48.png \
+ -C $(ICONDIR) altusmetrum-teststand-64.png \
+ -C $(ICONDIR) altusmetrum-teststand-128.png\
+ -C $(ICONDIR) altusmetrum-teststand-256.png
+
+WINDOWS_ICONS =\
+ ../icon/altusmetrum-teststand.ico \
+ ../icon/altusmetrum-teststand.exe
+ ../icon/application-vnd.altusmetrum.eeprom.ico \
+ ../icon/application-vnd.altusmetrum.eeprom.exe
+
+MACOSX_ICONS =\
+ ../icon/altusmetrum-teststand.icns \
+ ../icon/application-vnd.altusmetrum.eeprom.icns
+
+LINUX_ICONS =\
+ $(ICONDIR)/altusmetrum-altosui.svg \
+ $(ICONDIR)/application-vnd.altusmetrum.eeprom.svg
+
+LINUX_MIMETYPE =\
+ $(ICONDIR)/org-altusmetrum-mimetypes.xml
+
+# Firmware
+FIRMWARE_TD_3_0=$(top_srcdir)/src/teledongle-v3.0/teledongle-v3.0-$(VERSION).ihx
+FIRMWARE_TD=$(FIRMWARE_TD_3_0)
+
+FIRMWARE_TBT_3_0=$(top_srcdir)/src/telebt-v3.0/telebt-v3.0-$(VERSION).ihx
+FIRMWARE_TBT=$(FIRMWARE_TBT_3_0)
+
+FIRMWARE_TS_1_0=$(top_srcdir)/src/telefiretwo-v1.0/telefiretwo-v1.0-$(VERSION).ihx
+FIRMWARE_TS=$(FIRMWARE_TS_1_0)
+
+FIRMWARE=$(FIRMWARE_TS) $(FIRMWARE_TD) $(FIRMWARE_TBT)
+
+desktopdir = $(datadir)/applications
+desktop_file = altusmetrum-teststand.desktop
+desktop_SCRIPTS = $(desktop_file)
+
+all-local: teststand-test teststand-jdb $(JAR)
+
+clean-local:
+ -rm -rf classes $(JAR) $(FATJAR) \
+ TestStand-Linux-*.tar.bz2 TestStand-Mac-*.dmg TestStand-Windows-*.exe \
+ altoslib_*.jar altosuilib_*.jar \
+ $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(FREETTS_CLASS) $(LIBALTOS) Manifest.txt Manifest-fat.txt \
+ teststand teststand-test teststand-jdb macosx linux windows teststand-windows.log \
+ teststand-windows.nsi *.desktop
+
+EXTRA_DIST = $(desktop_file).in
+
+$(desktop_file): $(desktop_file).in
+ sed -e 's#%bindir%#@bindir@#' -e 's#%icondir%#$(datadir)/icons/hicolor/scalable/apps#' ${srcdir}/$(desktop_file).in > $@
+ chmod +x $@
+
+LINUX_DIST=TestStand-Linux-$(VERSION).tar.bz2
+LINUX_SH=TestStand-Linux-$(VERSION).sh
+MACOSX_DIST=TestStand-Mac-$(VERSION).dmg
+WINDOWS_DIST=TestStand-Windows-$(VERSION_DASH).exe
+
+TELEGPS_DOC=$(top_srcdir)/doc/teststand.pdf
+
+DOC=$(TELEGPS_DOC)
+
+FAT_FILES=$(FATJAR) $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS)
+
+LINUX_FILES=$(FAT_FILES) $(LIBALTOS_LINUX) $(FIRMWARE) $(DOC) $(desktop_file).in $(LINUX_ICONS) $(LINUX_MIMETYPE)
+LINUX_EXTRA=teststand-fat $(desktop_file).in
+
+MACOSX_INFO_PLIST=Info.plist
+MACOSX_README=ReadMe-Mac.rtf
+MACOSX_FILES=$(FAT_FILES) libaltos.dylib $(MACOSX_INFO_PLIST) $(MACOSX_README) $(DOC) $(MACOSX_ICONS)
+MACOSX_EXTRA=$(FIRMWARE)
+
+WINDOWS_FILES=$(FAT_FILES) altos.dll altos64.dll $(top_srcdir)/altusmetrum.inf $(top_srcdir)/altusmetrum.cat $(DOC) $(WINDOWS_ICONS)
+
+if FATINSTALL
+
+FATTARGET=$(FATDIR)/$(VERSION)
+
+LINUX_DIST_TARGET=$(FATTARGET)/$(LINUX_DIST)
+LINUX_SH_TARGET=$(FATTARGET)/$(LINUX_SH)
+MACOSX_DIST_TARGET=$(FATTARGET)/$(MACOSX_DIST)
+WINDOWS_DIST_TARGET=$(FATTARGET)/$(WINDOWS_DIST)
+
+fat: $(LINUX_DIST_TARGET) $(LINUX_SH_TARGET) $(MACOSX_DIST_TARGET) $(WINDOWS_DIST_TARGET)
+
+$(LINUX_DIST_TARGET): $(LINUX_DIST)
+ mkdir -p $(FATTARGET)
+ cp -p $< $@
+
+$(LINUX_SH_TARGET): $(LINUX_SH)
+ mkdir -p $(FATTARGET)
+ cp -p $< $@
+
+$(MACOSX_DIST_TARGET): $(MACOSX_DIST)
+ mkdir -p $(FATTARGET)
+ cp -p $< $@
+
+$(WINDOWS_DIST_TARGET): $(WINDOWS_DIST)
+ mkdir -p $(FATTARGET)
+ cp -p $< $@
+
+else
+fat: $(LINUX_DIST) $(LINUX_SH) $(MACOSX_DIST) $(WINDOWS_DIST)
+endif
+
+teststand: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -Djava.library.path="$(altoslibdir)" -jar "$(teststanddir)/teststand.jar" "$$@"' >> $@
+ chmod +x $@
+
+teststand-jdb: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec jdb -classpath "classes:./*:../libaltos:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar" -Djava.library.path="../libaltos/.libs" org.altusmetrum.teststand.TestStand "$$@"' >> $@
+ chmod +x $@
+
+teststand-test: Makefile
+ echo "#!/bin/sh" > $@
+ echo 'exec java -Djava.library.path="../libaltos/.libs" -jar teststand.jar "$$@"' >> $@
+ chmod +x $@
+
+install-teststandJAVA: teststand.jar
+ @$(NORMAL_INSTALL)
+ test -z "$(teststanddir)" || $(MKDIR_P) "$(DESTDIR)$(teststanddir)"
+ echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(teststanddir)/teststand.jar'"; \
+ $(INSTALL_DATA) "$<" "$(DESTDIR)$(teststanddir)"
+
+$(JAR): classteststand.stamp Manifest.txt $(JAVA_ICONS) $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS)
+ jar cfm $@ Manifest.txt \
+ $(ICONJAR) \
+ -C classes org \
+ -C ../libaltos libaltosJNI
+
+$(FATJAR): classteststand.stamp Manifest-fat.txt $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(JAVA_ICONS)
+ jar cfm $@ Manifest-fat.txt \
+ $(ICONJAR) \
+ -C classes org \
+ -C ../libaltos libaltosJNI
+
+libaltos.so: build-libaltos
+ -rm -f "$@"
+ $(LN_S) ../libaltos/.libs/"$@" .
+
+libaltos32.so: build-libaltos
+ -rm -f "$@"
+ $(LN_S) ../libaltos/.libs/"$@" .
+
+libaltos64.so: build-libaltos
+ -rm -f "$@"
+ $(LN_S) ../libaltos/.libs/"$@" .
+
+libaltos.dylib:
+ -rm -f "$@"
+ $(LN_S) ../libaltos/"$@" .
+
+altos.dll: ../libaltos/altos.dll
+ -rm -f "$@"
+ $(LN_S) ../libaltos/"$@" .
+
+altos64.dll: ../libaltos/altos64.dll
+ -rm -f "$@"
+ $(LN_S) ../libaltos/"$@" .
+
+../libaltos/.libs/libaltos64.so: ../libaltos/.libs/libaltos32.so
+
+../libaltos/.libs/libaltos32.so: build-libaltos
+
+../libaltos/.libs/libaltos.so: build-libaltos
+
+../libaltos/altos.dll: build-altos-dll
+
+../libaltos/altos64.dll: build-altos64-dll
+
+build-libaltos:
+ +cd ../libaltos && make libaltos.la
+build-altos-dll:
+ +cd ../libaltos && make altos.dll
+
+build-altos64-dll:
+ +cd ../libaltos && make altos64.dll
+
+$(ALTOSLIB_CLASS):
+ -rm -f "$@"
+ $(LN_S) ../altoslib/"$@" .
+
+$(ALTOSUILIB_CLASS):
+ -rm -f "$@"
+ $(LN_S) ../altosuilib/"$@" .
+
+$(FREETTS_CLASS):
+ -rm -f "$@"
+ $(LN_S) "$(FREETTS)"/"$@" .
+
+$(JFREECHART_CLASS):
+ -rm -f "$@"
+ $(LN_S) "$(JFREECHART)"/"$@" .
+
+$(JCOMMON_CLASS):
+ -rm -f "$@"
+ $(LN_S) "$(JCOMMON)"/"$@" .
+
+$(LINUX_DIST): $(LINUX_FILES) $(LINUX_EXTRA)
+ -rm -f $@
+ -rm -rf linux
+ mkdir -p linux/TestStand
+ cp -p $(LINUX_FILES) linux/TestStand
+ cp -p teststand-fat linux/TestStand/teststand
+ chmod +x linux/TestStand/teststand
+ tar cjf $@ -C linux TestStand
+
+$(LINUX_SH): $(LINUX_DIST) $(srcdir)/../altosui/linux-install.sh
+ sed 's/AltOS/TestStand/g' $(srcdir)/../altosui/linux-install.sh | cat - $(LINUX_DIST) > $@
+ chmod +x $@
+
+$(MACOSX_DIST): $(MACOSX_FILES) $(MACOSX_EXTRA) Makefile
+ -rm -f $@
+ -rm -rf macosx
+ mkdir macosx
+ cp -a TestStand.app macosx/
+ cp -a $(MACOSX_README) macosx/ReadMe.rtf
+ mkdir -p macosx/Doc
+ cp -a $(DOC) macosx/Doc
+ cp -p Info.plist macosx/TestStand.app/Contents
+ mkdir -p macosx/AltOS-$(VERSION) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(MACOSX_ICONS) macosx/TestStand.app/Contents/Resources
+ cp -p $(FATJAR) macosx/TestStand.app/Contents/Resources/Java/teststand.jar
+ cp -p libaltos.dylib macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(ALTOSLIB_CLASS) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(ALTOSUILIB_CLASS) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(FREETTS_CLASS) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(JFREECHART_CLASS) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(JCOMMON_CLASS) macosx/TestStand.app/Contents/Resources/Java
+ cp -p $(MACOSX_EXTRA) macosx/AltOS-$(VERSION)
+ genisoimage -D -V TestStand-$(VERSION) -no-pad -r -apple -o $@ macosx
+
+$(WINDOWS_DIST): $(WINDOWS_FILES) teststand-windows.nsi
+ -rm -f $@
+ makensis -Oteststand-windows.log "-XOutFile $@" "-DVERSION=$(VERSION)" teststand-windows.nsi || (cat teststand-windows.log && exit 1)
+
+Manifest.txt: Makefile
+ echo 'Main-Class: org.altusmetrum.teststand.TestStand' > $@
+ echo "Class-Path: $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) $(FREETTS)/freetts.jar $(JCOMMON)/jcommon.jar $(JFREECHART)/jfreechart.jar" >> $@
+
+Manifest-fat.txt:
+ echo 'Main-Class: org.altusmetrum.teststand.TestStand' > $@
+ echo "Class-Path: $(ALTOSLIB_CLASS) $(ALTOSUILIB_CLASS) freetts.jar jcommon.jar jfreechart.jar" >> $@
+
--- /dev/null
+{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510
+{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\margl1440\margr1440\vieww10800\viewh8400\viewkind0
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural
+
+\f0\fs24 \cf0 Installing TestStand software for Mac OS X computers\
+\
+There are two files included in the Mac OS X distribution:\
+\
+ 1) The TestStand application\
+\
+ 2) The FTDI device drivers\
+\
+As with most Mac OS X applications, install TestStand by dragging it from the distribution disk image to a suitable place on your computer.\
+\
+To communicate with the TestStand serial adapter, you need to installed the FTDI device drivers, which is done by double-clicking on the FTDIUSBSerialDriver disk image. Inside that is the FTDI USB Serial Driver package. Double click on that and it will guide you through the installation process.\
+\
+Thanks for choosing AltusMetrum products!}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.util.*;
+import java.text.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStand
+ extends AltosUIFrame
+ implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener
+{
+
+ static String[] teststand_icon_names = {
+ "/altusmetrum-teststand-16.png",
+ "/altusmetrum-teststand-32.png",
+ "/altusmetrum-teststand-48.png",
+ "/altusmetrum-teststand-64.png",
+ "/altusmetrum-teststand-128.png",
+ "/altusmetrum-teststand-256.png"
+ };
+
+ static { set_icon_names(teststand_icon_names); }
+
+ static AltosVoice voice;
+
+ static AltosVoice voice() {
+ if (voice == null)
+ voice = new AltosVoice();
+ return voice;
+ }
+
+ AltosFlightReader reader;
+ TestStandDisplayThread thread;
+ boolean idle_mode;
+
+ JMenuBar menu_bar;
+
+ JMenu file_menu;
+ JMenu monitor_menu;
+ JMenu device_menu;
+ AltosUIFreqList frequencies;
+ ActionListener frequency_listener;
+ AltosUIRateList rates;
+ ActionListener rate_listener;
+
+ Container bag;
+
+ TestStandStatus teststand_status;
+ TestStandStatusUpdate status_update;
+
+ JTabbedPane pane;
+
+ AltosUIMap map;
+ TestStandInfo gps_info;
+ TestStandState gps_state;
+ AltosInfoTable info_table;
+
+ LinkedList<AltosFlightDisplay> displays;
+
+ /* File menu */
+ final static String new_command = "new";
+ final static String graph_command = "graph";
+ final static String export_command = "export";
+ final static String load_maps_command = "loadmaps";
+ final static String preferences_command = "preferences";
+ final static String close_command = "close";
+ final static String exit_command = "exit";
+
+ static final String[][] file_menu_entries = new String[][] {
+ { "Graph Data", graph_command },
+ { "Export Data", export_command },
+ { "Load Maps", load_maps_command },
+ { "Preferences", preferences_command },
+ { "Close", close_command },
+ { "Exit", exit_command },
+ };
+
+ /* Monitor menu */
+ final static String connect_command = "connect";
+ final static String disconnect_command = "disconnect";
+ final static String scan_command = "scan";
+
+ static final String[][] monitor_menu_entries = new String[][] {
+ { "Connect Device", connect_command },
+ { "Disconnect", disconnect_command },
+ { "Scan Channels", scan_command },
+ };
+
+ /* Device menu */
+ final static String download_command = "download";
+ final static String configure_command = "configure";
+ final static String flash_command = "flash";
+
+ static final String[][] device_menu_entries = new String[][] {
+ { "Download Data", download_command },
+ { "Configure Device", configure_command },
+ { "Flash Device", flash_command },
+ };
+
+ void stop_display() {
+ if (thread != null && thread.isAlive()) {
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException ie) {}
+ }
+ thread = null;
+ }
+
+ public void reset() {
+ for (AltosFlightDisplay display : displays)
+ display.reset();
+ }
+
+ public void font_size_changed(int font_size) {
+ for (AltosFlightDisplay display : displays)
+ display.font_size_changed(font_size);
+ }
+
+ public void units_changed(boolean imperial_units) {
+ for (AltosFlightDisplay display : displays)
+ display.units_changed(imperial_units);
+ }
+
+ public void show(AltosState state, AltosListenerState listener_state) {
+ try {
+ status_update.saved_state = state;
+ status_update.saved_listener_state = listener_state;
+
+ if (state == null)
+ state = new AltosState();
+
+ int i = 0;
+ for (AltosFlightDisplay display : displays) {
+ display.show(state, listener_state);
+ i++;
+ }
+ } catch (Exception ex) {
+ System.out.printf("Exception %s\n", ex.toString());
+ for (StackTraceElement e : ex.getStackTrace())
+ System.out.printf("%s\n", e.toString());
+ }
+ }
+
+ void preferences() {
+ new TestStandPreferences(this, voice());
+ }
+
+ void load_maps() {
+ new AltosUIMapPreload(this);
+ }
+
+ void disconnect() {
+ setTitle("TestStand");
+ stop_display();
+ teststand_status.stop();
+
+ teststand_status.disable_receive();
+ disable_frequency_menu();
+ disable_rate_menu();
+ }
+
+ void connect_flight(AltosDevice device) {
+ try {
+ AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device));
+ set_reader(reader, device, false);
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(this,
+ ee.getMessage(),
+ String.format ("Cannot open %s", device.toShortString()),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (AltosSerialInUseException si) {
+ JOptionPane.showMessageDialog(this,
+ String.format("Device \"%s\" already in use",
+ device.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(this,
+ String.format ("Unknown I/O error on %s", device.toShortString()),
+ "Unknown I/O error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (TimeoutException te) {
+ JOptionPane.showMessageDialog(this,
+ String.format ("Timeout on %s", device.toShortString()),
+ "Timeout error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (InterruptedException ie) {
+ JOptionPane.showMessageDialog(this,
+ String.format("Interrupted %s", device.toShortString()),
+ "Interrupted exception",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ void connect_idle(AltosDevice device) {
+ try {
+ AltosFlightReader reader = new AltosIdleReader(new AltosSerial(device), false);
+ set_reader(reader, device, true);
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(this,
+ ee.getMessage(),
+ String.format ("Cannot open %s", device.toShortString()),
+ JOptionPane.ERROR_MESSAGE);
+ } catch (AltosSerialInUseException si) {
+ JOptionPane.showMessageDialog(this,
+ String.format("Device \"%s\" already in use",
+ device.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(this,
+ String.format ("Unknown I/O error on %s", device.toShortString()),
+ "Unknown I/O error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (TimeoutException te) {
+ JOptionPane.showMessageDialog(this,
+ String.format ("Timeout on %s", device.toShortString()),
+ "Timeout error",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (InterruptedException ie) {
+ JOptionPane.showMessageDialog(this,
+ String.format("Interrupted %s", device.toShortString()),
+ "Interrupted exception",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ void connect(AltosDevice device) {
+ if (reader != null)
+ disconnect();
+ if (device.matchProduct(AltosLib.product_basestation))
+ connect_flight(device);
+ else
+ connect_idle(device);
+ }
+
+ void connect() {
+ AltosDevice device = AltosDeviceUIDialog.show(this,
+ AltosLib.product_any);
+ if (device == null)
+ return;
+ connect(device);
+ }
+
+ public void scan_device_selected(AltosDevice device) {
+ connect(device);
+ }
+
+ void scan() {
+ new AltosScanUI(this, false);
+ }
+
+ void download(){
+ new AltosEepromManage(this, AltosLib.product_any);
+ }
+
+ void configure() {
+ new TestStandConfig(this);
+ }
+
+ void export() {
+ AltosDataChooser chooser;
+ chooser = new AltosDataChooser(this);
+ AltosStateIterable states = chooser.runDialog();
+ if (states == null)
+ return;
+ new AltosCSVUI(this, states, chooser.file());
+ }
+
+ void graph() {
+ AltosDataChooser chooser;
+ chooser = new AltosDataChooser(this);
+ AltosStateIterable states = chooser.runDialog();
+ if (states == null)
+ return;
+ try {
+ new TestStandGraphUI(states, chooser.file());
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ }
+
+ void flash() {
+ AltosFlashUI.show(this);
+ }
+
+ public void actionPerformed(ActionEvent ev) {
+
+ /* File menu */
+ if (preferences_command.equals(ev.getActionCommand())) {
+ preferences();
+ return;
+ }
+ if (load_maps_command.equals(ev.getActionCommand())) {
+ load_maps();
+ return;
+ }
+ if (close_command.equals(ev.getActionCommand())) {
+ close();
+ return;
+ }
+ if (exit_command.equals(ev.getActionCommand()))
+ System.exit(0);
+
+ /* Monitor menu */
+ if (connect_command.equals(ev.getActionCommand())) {
+ connect();
+ return;
+ }
+ if (disconnect_command.equals(ev.getActionCommand())) {
+ disconnect();
+ return;
+ }
+ if (scan_command.equals(ev.getActionCommand())) {
+ scan();
+ return;
+ }
+
+ /* Device menu */
+ if (download_command.equals(ev.getActionCommand())) {
+ download();
+ return;
+ }
+ if (configure_command.equals(ev.getActionCommand())) {
+ configure();
+ return;
+ }
+ if (export_command.equals(ev.getActionCommand())) {
+ export();
+ return;
+ }
+ if (graph_command.equals(ev.getActionCommand())) {
+ graph();
+ return;
+ }
+ if (flash_command.equals(ev.getActionCommand())) {
+ flash();
+ return;
+ }
+ }
+
+ void enable_frequency_menu(int serial, final AltosFlightReader reader) {
+
+ if (frequency_listener != null)
+ disable_frequency_menu();
+
+ frequency_listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ double frequency = frequencies.frequency();
+ try {
+ reader.set_frequency(frequency);
+ } catch (TimeoutException te) {
+ } catch (InterruptedException ie) {
+ }
+ reader.save_frequency();
+ }
+ };
+
+ frequencies.addActionListener(frequency_listener);
+ frequencies.set_product("Monitor");
+ frequencies.set_serial(serial);
+ frequencies.set_frequency(AltosUIPreferences.frequency(serial));
+ frequencies.setEnabled(true);
+
+ }
+
+ void disable_frequency_menu() {
+ if (frequency_listener != null) {
+ frequencies.removeActionListener(frequency_listener);
+ frequencies.setEnabled(false);
+ frequency_listener = null;
+ }
+
+ }
+
+ void enable_rate_menu(int serial, final AltosFlightReader reader) {
+
+ if (rate_listener != null)
+ disable_rate_menu();
+
+ rate_listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int rate = rates.rate();
+ try {
+ reader.set_telemetry_rate(rate);
+ } catch (TimeoutException te) {
+ } catch (InterruptedException ie) {
+ }
+ reader.save_telemetry_rate();
+ }
+ };
+
+ rates.addActionListener(rate_listener);
+ rates.set_product("Monitor");
+ rates.set_serial(serial);
+ rates.set_rate(AltosUIPreferences.telemetry_rate(serial));
+ rates.setEnabled(reader.supports_telemetry_rate(AltosLib.ao_telemetry_rate_2400));
+ }
+
+ void disable_rate_menu() {
+ if (rate_listener != null) {
+ rates.removeActionListener(rate_listener);
+ rates.setEnabled(false);
+ rate_listener = null;
+ }
+
+ }
+
+ public void set_reader(AltosFlightReader reader, AltosDevice device, boolean idle_mode) {
+ this.idle_mode = idle_mode;
+ status_update = new TestStandStatusUpdate(teststand_status);
+
+ teststand_status.start(status_update);
+
+ setTitle(String.format("TestStand %s", reader.name));
+ thread = new TestStandDisplayThread(this, voice(), this, reader);
+ thread.start();
+
+ if (device != null) {
+ if (idle_mode) {
+ disable_frequency_menu();
+ disable_rate_menu();
+ } else {
+ enable_frequency_menu(device.getSerial(), reader);
+ enable_rate_menu(device.getSerial(), reader);
+ }
+ }
+ }
+
+ static int number_of_windows;
+
+ static public void add_window() {
+ ++number_of_windows;
+ }
+
+ static public void subtract_window() {
+ --number_of_windows;
+ if (number_of_windows == 0)
+ System.exit(0);
+ }
+
+ private void close() {
+ disconnect();
+ AltosUIPreferences.unregister_font_listener(this);
+ AltosPreferences.unregister_units_listener(this);
+ setVisible(false);
+ dispose();
+ subtract_window();
+ }
+
+ private void add_menu(JMenu menu, String label, String action) {
+ JMenuItem item = new JMenuItem(label);
+ menu.add(item);
+ item.addActionListener(this);
+ item.setActionCommand(action);
+ }
+
+
+ private JMenu make_menu(String label, String[][] items) {
+ JMenu menu = new JMenu(label);
+ for (int i = 0; i < items.length; i++) {
+ if (MAC_OS_X) {
+ if (items[i][1].equals("exit"))
+ continue;
+ if (items[i][1].equals("preferences"))
+ continue;
+ }
+ add_menu(menu, items[i][0], items[i][1]);
+ }
+ menu_bar.add(menu);
+ return menu;
+ }
+
+ /* OSXAdapter interfaces */
+ public void macosx_file_handler(String path) {
+ process_graph(new File(path));
+ }
+
+ public void macosx_quit_handler() {
+ System.exit(0);
+ }
+
+ public void macosx_preferences_handler() {
+ preferences();
+ }
+
+ public TestStand() {
+
+ AltosUIPreferences.set_component(this);
+
+ register_for_macosx_events();
+
+ reader = null;
+
+ bag = getContentPane();
+ bag.setLayout(new GridBagLayout());
+
+ setTitle("TestStand");
+
+ menu_bar = new JMenuBar();
+ setJMenuBar(menu_bar);
+
+ file_menu = make_menu("File", file_menu_entries);
+ monitor_menu = make_menu("Monitor", monitor_menu_entries);
+ device_menu = make_menu("Device", device_menu_entries);
+
+ set_inset(3);
+ frequencies = new AltosUIFreqList();
+ frequencies.setEnabled(false);
+ bag.add(frequencies, constraints (0, 1));
+
+ rates = new AltosUIRateList();
+ rates.setEnabled(false);
+ bag.add(rates, constraints(1, 1));
+ next_row();
+ set_inset(0);
+
+ displays = new LinkedList<AltosFlightDisplay>();
+
+ int serial = -1;
+
+ /* TestStand status is always visible */
+ teststand_status = new TestStandStatus();
+ bag.add(teststand_status, constraints(0, 3, GridBagConstraints.HORIZONTAL));
+ next_row();
+
+ displays.add(teststand_status);
+
+
+ /* The rest of the window uses a tabbed pane to
+ * show one of the alternate data views
+ */
+ pane = new JTabbedPane();
+
+ /* Make the tabbed pane use the rest of the window space */
+ bag.add(pane, constraints(0, 3, GridBagConstraints.BOTH));
+
+ map = new AltosUIMap();
+ pane.add(map.getName(), map);
+ displays.add(map);
+
+ gps_info = new TestStandInfo();
+ pane.add(gps_info.getName(), gps_info);
+ displays.add(gps_info);
+
+ gps_state = new TestStandState();
+ pane.add(gps_state.getName(), gps_state);
+ displays.add(gps_state);
+
+ info_table = new AltosInfoTable();
+ pane.add("Table", info_table);
+ displays.add(info_table);
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ AltosUIPreferences.register_font_listener(this);
+ AltosPreferences.register_units_listener(this);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ close();
+ }
+ });
+
+ pack();
+ setVisible(true);
+
+ add_window();
+ }
+
+ public TestStand(AltosFlightReader reader, boolean idle_mode) {
+ this();
+ set_reader(reader, null, idle_mode);
+ }
+
+ public TestStand(AltosDevice device) {
+ this();
+ connect(device);
+ }
+
+ static AltosStateIterable record_iterable(File file) {
+ FileInputStream in;
+ try {
+ in = new FileInputStream(file);
+ } catch (Exception e) {
+ System.out.printf("Failed to open file '%s'\n", file);
+ return null;
+ }
+ if (file.getName().endsWith("telem"))
+ return new AltosTelemetryFile(in);
+ else
+ return new AltosEepromFile(in);
+ }
+
+ static AltosReplayReader replay_file(File file) {
+ AltosStateIterable states = record_iterable(file);
+ if (states == null)
+ return null;
+ return new AltosReplayReader(states.iterator(), file);
+ }
+
+ static boolean process_graph(File file) {
+ AltosStateIterable states = record_iterable(file);
+ if (states == null)
+ return false;
+ try {
+ new TestStandGraphUI(states, file);
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ static boolean process_replay(File file) {
+ AltosReplayReader new_reader = replay_file(file);
+ if (new_reader == null)
+ return false;
+
+ new TestStand(new_reader, true);
+ return true;
+ }
+
+ static final int process_none = 0;
+ static final int process_csv = 1;
+ static final int process_kml = 2;
+ static final int process_graph = 3;
+ static final int process_replay = 4;
+ static final int process_summary = 5;
+ static final int process_cat = 6;
+
+ public static boolean load_library(Frame frame) {
+ if (!AltosUILib.load_library()) {
+ JOptionPane.showMessageDialog(frame,
+ String.format("No AltOS library in \"%s\"",
+ System.getProperty("java.library.path","<undefined>")),
+ "Cannot load device access library",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ public static void help(int code) {
+ System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
+ System.out.printf(" Options:\n");
+ System.out.printf(" --replay <filename>\t\trelive the glory of past flights \n");
+ System.out.printf(" --graph <filename>\t\tgraph a flight\n");
+ System.out.printf(" --csv\tgenerate comma separated output for spreadsheets, etc\n");
+ System.out.printf(" --kml\tgenerate KML output for use with Google Earth\n");
+ System.exit(code);
+ }
+
+ public static void main(String[] args) {
+ int errors = 0;
+
+ load_library(null);
+ try {
+ UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
+ } catch (Exception e) {
+ }
+
+ boolean any_created = false;
+
+
+ /* Handle batch-mode */
+ int process = process_none;
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--help"))
+ help(0);
+ else if (args[i].equals("--replay"))
+ process = process_replay;
+ else if (args[i].equals("--kml"))
+ process = process_kml;
+ else if (args[i].equals("--csv"))
+ process = process_csv;
+ else if (args[i].equals("--graph"))
+ process = process_graph;
+ else if (args[i].equals("--summary"))
+ process = process_summary;
+ else if (args[i].equals("--cat"))
+ process = process_cat;
+ else if (args[i].startsWith("--"))
+ help(1);
+ else {
+ File file = new File(args[i]);
+ switch (process) {
+ case process_none:
+ case process_graph:
+ if (!process_graph(file))
+ ++errors;
+ break;
+ case process_replay:
+ if (!process_replay(file))
+ ++errors;
+ any_created = true;
+ break;
+ case process_kml:
+ ++errors;
+ break;
+ case process_csv:
+ ++errors;
+ break;
+ case process_summary:
+ ++errors;
+ break;
+ case process_cat:
+ ++errors;
+ }
+ }
+ }
+ if (errors != 0)
+ System.exit(errors);
+ if (number_of_windows == 0) {
+ java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
+ if (devices != null)
+ for (AltosDevice device : devices) {
+ new TestStand(device);
+ any_created = true;
+ }
+ if (number_of_windows == 0)
+ new TestStand();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.text.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandConfig implements ActionListener {
+
+ class int_ref {
+ int value;
+
+ public int get() {
+ return value;
+ }
+ public void set(int i) {
+ value = i;
+ }
+ public int_ref(int i) {
+ value = i;
+ }
+ }
+
+ class string_ref {
+ String value;
+
+ public String get() {
+ return value;
+ }
+ public void set(String i) {
+ value = i;
+ }
+ public string_ref(String i) {
+ value = i;
+ }
+ }
+
+ JFrame owner;
+ AltosDevice device;
+ AltosSerial serial_line;
+
+ AltosConfigData data;
+ TestStandConfigUI config_ui;
+ boolean serial_started;
+ boolean made_visible;
+
+ void start_serial() throws InterruptedException, TimeoutException {
+ serial_started = true;
+ }
+
+ void stop_serial() throws InterruptedException {
+ if (!serial_started)
+ return;
+ serial_started = false;
+ }
+
+ void update_ui() {
+ data.set_values(config_ui);
+ config_ui.set_clean();
+ if (!made_visible) {
+ made_visible = true;
+ config_ui.make_visible();
+ }
+ }
+
+ int pyro;
+
+ final static int serial_mode_read = 0;
+ final static int serial_mode_save = 1;
+ final static int serial_mode_reboot = 2;
+
+ class SerialData implements Runnable {
+ TestStandConfig config;
+ int serial_mode;
+
+ void callback(String in_cmd) {
+ final String cmd = in_cmd;
+ Runnable r = new Runnable() {
+ public void run() {
+ if (cmd.equals("abort")) {
+ abort();
+ } else if (cmd.equals("all finished")) {
+ if (serial_line != null)
+ update_ui();
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ void get_data() {
+ data = null;
+ try {
+ start_serial();
+ data = new AltosConfigData(config.serial_line);
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ try {
+ stop_serial();
+ callback("abort");
+ } catch (InterruptedException ie) {
+ }
+ } finally {
+ try {
+ stop_serial();
+ } catch (InterruptedException ie) {
+ }
+ }
+ callback("all finished");
+ }
+
+ void save_data() {
+ try {
+ start_serial();
+ data.save(serial_line, false);
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ } finally {
+ try {
+ stop_serial();
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+
+ void reboot() {
+ try {
+ start_serial();
+ serial_line.printf("r eboot\n");
+ serial_line.flush_output();
+ } catch (InterruptedException ie) {
+ } catch (TimeoutException te) {
+ } finally {
+ try {
+ stop_serial();
+ serial_line.close();
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+
+ public void run () {
+ switch (serial_mode) {
+ case serial_mode_save:
+ save_data();
+ /* fall through ... */
+ case serial_mode_read:
+ get_data();
+ break;
+ case serial_mode_reboot:
+ reboot();
+ break;
+ }
+ }
+
+ public SerialData(TestStandConfig in_config, int in_serial_mode) {
+ config = in_config;
+ serial_mode = in_serial_mode;
+ }
+ }
+
+ void run_serial_thread(int serial_mode) {
+ SerialData sd = new SerialData(this, serial_mode);
+ Thread st = new Thread(sd);
+ st.start();
+ }
+
+ void init_ui () throws InterruptedException, TimeoutException {
+ config_ui = new TestStandConfigUI(owner);
+ config_ui.addActionListener(this);
+ serial_line.set_frame(owner);
+ set_ui();
+ }
+
+ void abort() {
+ if (serial_line != null) {
+ serial_line.close();
+ serial_line = null;
+ }
+ JOptionPane.showMessageDialog(owner,
+ String.format("Connection to \"%s\" failed",
+ device.toShortString()),
+ "Connection Failed",
+ JOptionPane.ERROR_MESSAGE);
+ config_ui.setVisible(false);
+ }
+
+ void set_ui() throws InterruptedException, TimeoutException {
+ if (serial_line != null)
+ run_serial_thread(serial_mode_read);
+ else
+ update_ui();
+ }
+
+ double frequency() {
+ return AltosConvert.radio_to_frequency(data.radio_frequency,
+ data.radio_setting,
+ data.radio_calibration,
+ data.radio_channel);
+ }
+
+ void save_data() {
+
+ try {
+ /* bounds check stuff */
+ if (config_ui.flight_log_max() > data.log_space()/1024) {
+ JOptionPane.showMessageDialog(owner,
+ String.format("Requested flight log, %dk, is larger than the available space, %dk.\n",
+ config_ui.flight_log_max(),
+ data.log_space()/1024),
+ "Maximum Flight Log Too Large",
+ JOptionPane.ERROR_MESSAGE);
+ return;
+ }
+
+ /* Pull data out of the UI and stuff back into our local data record */
+
+ data.get_values(config_ui);
+ run_serial_thread(serial_mode_save);
+ } catch (AltosConfigDataException ae) {
+ JOptionPane.showMessageDialog(owner,
+ ae.getMessage(),
+ "Configuration Data Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+ try {
+ if (cmd.equals("Save")) {
+ save_data();
+ } else if (cmd.equals("Reset")) {
+ set_ui();
+ } else if (cmd.equals("Reboot")) {
+ if (serial_line != null)
+ run_serial_thread(serial_mode_reboot);
+ } else if (cmd.equals("Close")) {
+ if (serial_line != null)
+ serial_line.close();
+ }
+ } catch (InterruptedException ie) {
+ abort();
+ } catch (TimeoutException te) {
+ abort();
+ }
+ }
+
+ public TestStandConfig(JFrame given_owner) {
+ owner = given_owner;
+
+ device = AltosDeviceUIDialog.show(owner, AltosLib.product_any);
+ if (device != null) {
+ try {
+ serial_line = new AltosSerial(device);
+ try {
+ init_ui();
+ } catch (InterruptedException ie) {
+ abort();
+ } catch (TimeoutException te) {
+ abort();
+ }
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(owner,
+ ee.getMessage(),
+ "Cannot open target device",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (AltosSerialInUseException si) {
+ JOptionPane.showMessageDialog(owner,
+ String.format("Device \"%s\" already in use",
+ device.toShortString()),
+ "Device in use",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.text.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandConfigUI
+ extends AltosUIDialog
+ implements ActionListener, ItemListener, DocumentListener, AltosConfigValues, AltosUnitsListener
+{
+
+ Container pane;
+ JLabel product_label;
+ JLabel version_label;
+ JLabel serial_label;
+ JLabel frequency_label;
+ JLabel radio_calibration_label;
+ JLabel radio_frequency_label;
+ JLabel radio_enable_label;
+ JLabel rate_label;
+ JLabel aprs_interval_label;
+ JLabel aprs_ssid_label;
+ JLabel aprs_format_label;
+ JLabel flight_log_max_label;
+ JLabel callsign_label;
+ JLabel tracker_motion_label;
+ JLabel tracker_interval_label;
+
+ public boolean dirty;
+
+ JFrame owner;
+ JLabel product_value;
+ JLabel version_value;
+ JLabel serial_value;
+ AltosUIFreqList radio_frequency_value;
+ JLabel radio_calibration_value;
+ JRadioButton radio_enable_value;
+ AltosUIRateList rate_value;
+ JComboBox<String> aprs_interval_value;
+ JComboBox<Integer> aprs_ssid_value;
+ JComboBox<String> aprs_format_value;
+ JComboBox<String> flight_log_max_value;
+ JTextField callsign_value;
+ JComboBox<String> tracker_motion_value;
+ JComboBox<String> tracker_interval_value;
+
+ JButton save;
+ JButton reset;
+ JButton reboot;
+ JButton close;
+
+ ActionListener listener;
+
+ static String[] aprs_interval_values = {
+ "Disabled",
+ "2",
+ "5",
+ "10"
+ };
+
+ static Integer[] aprs_ssid_values = {
+ 0, 1, 2 ,3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ };
+
+ static String[] tracker_motion_values_m = {
+ "2",
+ "5",
+ "10",
+ "25",
+ };
+
+ static String[] tracker_motion_values_ft = {
+ "5",
+ "20",
+ "50",
+ "100"
+ };
+
+ static String[] tracker_interval_values = {
+ "1",
+ "2",
+ "5",
+ "10"
+ };
+
+ /* A window listener to catch closing events and tell the config code */
+ class ConfigListener extends WindowAdapter {
+ TestStandConfigUI ui;
+
+ public ConfigListener(TestStandConfigUI this_ui) {
+ ui = this_ui;
+ }
+
+ public void windowClosing(WindowEvent e) {
+ ui.actionPerformed(new ActionEvent(e.getSource(),
+ ActionEvent.ACTION_PERFORMED,
+ "Close"));
+ }
+ }
+
+ public void set_pyros(AltosPyro[] new_pyros) {
+ }
+
+ public AltosPyro[] pyros() {
+ return null;
+ }
+
+ public void set_pyro_firing_time(double new_pyro_firing_time) {
+ }
+
+ public double pyro_firing_time() {
+ return -1;
+ }
+
+ boolean is_telemetrum() {
+ String product = product_value.getText();
+ return product != null && product.startsWith("TestStand");
+ }
+
+ void set_radio_enable_tool_tip() {
+ if (radio_enable_value.isEnabled())
+ radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions");
+ else
+ radio_enable_value.setToolTipText("Firmware version does not support disabling radio");
+ }
+
+ void set_rate_tool_tip() {
+ if (rate_value.isEnabled())
+ rate_value.setToolTipText("Select telemetry baud rate");
+ else
+ rate_value.setToolTipText("Firmware version does not support variable telemetry rates");
+ }
+
+ void set_aprs_interval_tool_tip() {
+ if (aprs_interval_value.isEnabled())
+ aprs_interval_value.setToolTipText("Enable APRS and set the interval between APRS reports");
+ else
+ aprs_interval_value.setToolTipText("Hardware doesn't support APRS");
+ }
+
+ void set_aprs_ssid_tool_tip() {
+ if (aprs_ssid_value.isEnabled())
+ aprs_ssid_value.setToolTipText("Set the APRS SSID (secondary station identifier)");
+ else if (aprs_ssid_value.isEnabled())
+ aprs_ssid_value.setToolTipText("Software version doesn't support setting the APRS SSID");
+ else
+ aprs_ssid_value.setToolTipText("Hardware doesn't support APRS");
+ }
+
+ void set_aprs_format_tool_tip() {
+ if (aprs_format_value.isEnabled())
+ aprs_format_value.setToolTipText("Set the APRS format (compressed/uncompressed)");
+ else if (aprs_format_value.isEnabled())
+ aprs_format_value.setToolTipText("Software version doesn't support setting the APRS format");
+ else
+ aprs_format_value.setToolTipText("Hardware doesn't support APRS");
+ }
+
+ void set_flight_log_max_tool_tip() {
+ if (flight_log_max_value.isEnabled())
+ flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)");
+ else
+ flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory");
+ }
+
+ /* Build the UI using a grid bag */
+ public TestStandConfigUI(JFrame in_owner) {
+ super (in_owner, "Configure Device", false);
+
+ owner = in_owner;
+ GridBagConstraints c;
+ int row = 0;
+
+ Insets il = new Insets(4,4,4,4);
+ Insets ir = new Insets(4,4,4,4);
+
+ pane = getContentPane();
+ pane.setLayout(new GridBagLayout());
+
+ /* Product */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ product_label = new JLabel("Product:");
+ pane.add(product_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ product_value = new JLabel("");
+ pane.add(product_value, c);
+ row++;
+
+ /* Version */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ version_label = new JLabel("Software version:");
+ pane.add(version_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ version_value = new JLabel("");
+ pane.add(version_value, c);
+ row++;
+
+ /* Serial */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ serial_label = new JLabel("Serial:");
+ pane.add(serial_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ serial_value = new JLabel("");
+ pane.add(serial_value, c);
+ row++;
+
+ /* Frequency */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ radio_frequency_label = new JLabel("Frequency:");
+ pane.add(radio_frequency_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ radio_frequency_value = new AltosUIFreqList();
+ radio_frequency_value.addItemListener(this);
+ pane.add(radio_frequency_value, c);
+ radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency");
+ row++;
+
+ /* Radio Calibration */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ radio_calibration_label = new JLabel("RF Calibration:");
+ pane.add(radio_calibration_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ radio_calibration_value = new JLabel(String.format("%d", 1186611));
+ pane.add(radio_calibration_value, c);
+ row++;
+
+ /* Radio Enable */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ radio_enable_label = new JLabel("Telemetry/RDF/APRS Enable:");
+ pane.add(radio_enable_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ radio_enable_value = new JRadioButton("Enabled");
+ radio_enable_value.addItemListener(this);
+ pane.add(radio_enable_value, c);
+ set_radio_enable_tool_tip();
+ row++;
+
+ /* Telemetry Rate */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ rate_label = new JLabel("Telemetry baud rate:");
+ pane.add(rate_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ rate_value = new AltosUIRateList();
+ rate_value.addItemListener(this);
+ pane.add(rate_value, c);
+ set_rate_tool_tip();
+ row++;
+
+ /* APRS interval */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ aprs_interval_label = new JLabel("APRS Interval(s):");
+ pane.add(aprs_interval_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ aprs_interval_value = new JComboBox<String>(aprs_interval_values);
+ aprs_interval_value.setEditable(true);
+ aprs_interval_value.addItemListener(this);
+ pane.add(aprs_interval_value, c);
+ set_aprs_interval_tool_tip();
+ row++;
+
+ /* APRS SSID */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ aprs_ssid_label = new JLabel("APRS SSID:");
+ pane.add(aprs_ssid_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ aprs_ssid_value = new JComboBox<Integer>(aprs_ssid_values);
+ aprs_ssid_value.setEditable(false);
+ aprs_ssid_value.addItemListener(this);
+ aprs_ssid_value.setMaximumRowCount(aprs_ssid_values.length);
+ pane.add(aprs_ssid_value, c);
+ set_aprs_ssid_tool_tip();
+ row++;
+
+ /* APRS format */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ aprs_format_label = new JLabel("APRS format:");
+ pane.add(aprs_format_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ aprs_format_value = new JComboBox<String>(AltosLib.ao_aprs_format_name);
+ aprs_format_value.setEditable(false);
+ aprs_format_value.addItemListener(this);
+ aprs_format_value.setMaximumRowCount(AltosLib.ao_aprs_format_name.length);
+ pane.add(aprs_format_value, c);
+ set_aprs_format_tool_tip();
+ row++;
+
+ /* Callsign */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ callsign_label = new JLabel("Callsign:");
+ pane.add(callsign_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ callsign_value = new JTextField(AltosUIPreferences.callsign());
+ callsign_value.getDocument().addDocumentListener(this);
+ pane.add(callsign_value, c);
+ callsign_value.setToolTipText("Callsign reported in telemetry data");
+ row++;
+
+ /* Flight log max */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ flight_log_max_label = new JLabel("Maximum Log Size (kB):");
+ pane.add(flight_log_max_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ flight_log_max_value = new JComboBox<String>();
+ flight_log_max_value.setEditable(true);
+ flight_log_max_value.addItemListener(this);
+ pane.add(flight_log_max_value, c);
+ set_flight_log_max_tool_tip();
+ row++;
+
+ /* Tracker triger horiz distances */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ tracker_motion_label = new JLabel(get_tracker_motion_label());
+ pane.add(tracker_motion_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ tracker_motion_value = new JComboBox<String>(tracker_motion_values());
+ tracker_motion_value.setEditable(true);
+ tracker_motion_value.addItemListener(this);
+ pane.add(tracker_motion_value, c);
+ row++;
+
+ /* Tracker triger vert distances */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ c.ipady = 5;
+ tracker_interval_label = new JLabel("Position Reporting Interval (s):");
+ pane.add(tracker_interval_label, c);
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 4;
+ c.fill = GridBagConstraints.HORIZONTAL;
+ c.weightx = 1;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = ir;
+ c.ipady = 5;
+ tracker_interval_value = new JComboBox<String>(tracker_interval_values);
+ tracker_interval_value.setEditable(true);
+ tracker_interval_value.addItemListener(this);
+ pane.add(tracker_interval_value, c);
+ set_tracker_tool_tip();
+ row++;
+
+ /* Buttons */
+ c = new GridBagConstraints();
+ c.gridx = 0; c.gridy = row;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_START;
+ c.insets = il;
+ save = new JButton("Save");
+ pane.add(save, c);
+ save.addActionListener(this);
+ save.setActionCommand("Save");
+
+ c = new GridBagConstraints();
+ c.gridx = 2; c.gridy = row;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = il;
+ reset = new JButton("Reset");
+ pane.add(reset, c);
+ reset.addActionListener(this);
+ reset.setActionCommand("Reset");
+
+ c = new GridBagConstraints();
+ c.gridx = 4; c.gridy = row;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.CENTER;
+ c.insets = il;
+ reboot = new JButton("Reboot");
+ pane.add(reboot, c);
+ reboot.addActionListener(this);
+ reboot.setActionCommand("Reboot");
+
+ c = new GridBagConstraints();
+ c.gridx = 6; c.gridy = row;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.NONE;
+ c.anchor = GridBagConstraints.LINE_END;
+ c.insets = il;
+ close = new JButton("Close");
+ pane.add(close, c);
+ close.addActionListener(this);
+ close.setActionCommand("Close");
+
+ addWindowListener(new ConfigListener(this));
+ AltosPreferences.register_units_listener(this);
+ }
+
+ /* Once the initial values are set, the config code will show the dialog */
+ public void make_visible() {
+ pack();
+ setLocationRelativeTo(owner);
+ setVisible(true);
+ }
+
+ /* If any values have been changed, confirm before closing */
+ public boolean check_dirty(String operation) {
+ if (dirty) {
+ Object[] options = { String.format("%s anyway", operation), "Keep editing" };
+ int i;
+ i = JOptionPane.showOptionDialog(this,
+ String.format("Configuration modified. %s anyway?", operation),
+ "Configuration Modified",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.WARNING_MESSAGE,
+ null, options, options[1]);
+ if (i != 0)
+ return false;
+ }
+ return true;
+ }
+
+ void set_dirty() {
+ dirty = true;
+ save.setEnabled(true);
+ }
+
+ public void set_clean() {
+ dirty = false;
+ save.setEnabled(false);
+ }
+
+ public void dispose() {
+ AltosPreferences.unregister_units_listener(this);
+ super.dispose();
+ }
+
+ /* Listen for events from our buttons */
+ public void actionPerformed(ActionEvent e) {
+ String cmd = e.getActionCommand();
+
+ if (cmd.equals("Close") || cmd.equals("Reboot"))
+ if (!check_dirty(cmd))
+ return;
+ listener.actionPerformed(e);
+ if (cmd.equals("Close") || cmd.equals("Reboot")) {
+ setVisible(false);
+ dispose();
+ }
+ set_clean();
+ }
+
+ /* ItemListener interface method */
+ public void itemStateChanged(ItemEvent e) {
+ set_dirty();
+ }
+
+ /* DocumentListener interface methods */
+ public void changedUpdate(DocumentEvent e) {
+ set_dirty();
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ set_dirty();
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ set_dirty();
+ }
+
+ /* Let the config code hook on a listener */
+ public void addActionListener(ActionListener l) {
+ listener = l;
+ }
+
+ public void units_changed(boolean imperial_units) {
+ boolean was_dirty = dirty;
+
+ if (tracker_motion_value.isEnabled()) {
+ String motion = tracker_motion_value.getSelectedItem().toString();
+ tracker_motion_label.setText(get_tracker_motion_label());
+ set_tracker_motion_values();
+ try {
+ int m = (int) (AltosConvert.height.parse_locale(motion, !imperial_units) + 0.5);
+ set_tracker_motion(m);
+ } catch (ParseException pe) {
+ }
+ }
+ if (!was_dirty)
+ set_clean();
+ }
+
+ /* set and get all of the dialog values */
+ public void set_product(String product) {
+ radio_frequency_value.set_product(product);
+ product_value.setText(product);
+ set_flight_log_max_tool_tip();
+ }
+
+ public void set_version(String version) {
+ version_value.setText(version);
+ }
+
+ public void set_serial(int serial) {
+ radio_frequency_value.set_serial(serial);
+ serial_value.setText(String.format("%d", serial));
+ }
+
+ public void set_altitude_32(int altitude_32) {
+ }
+
+ public void set_main_deploy(int new_main_deploy) {
+ }
+
+ public int main_deploy() {
+ return -1;
+ }
+
+ public void set_apogee_delay(int new_apogee_delay) { }
+
+ public int apogee_delay() {
+ return -1;
+ }
+
+ public void set_apogee_lockout(int new_apogee_lockout) { }
+
+ public int apogee_lockout() { return -1; }
+
+ public void set_radio_frequency(double new_radio_frequency) {
+ radio_frequency_value.set_frequency(new_radio_frequency);
+ }
+
+ public double radio_frequency() {
+ return radio_frequency_value.frequency();
+ }
+
+ public void set_radio_calibration(int new_radio_calibration) {
+ radio_calibration_value.setVisible(new_radio_calibration >= 0);
+ if (new_radio_calibration < 0)
+ radio_calibration_value.setText("Disabled");
+ else
+ radio_calibration_value.setText(String.format("%d", new_radio_calibration));
+ }
+
+ private int parse_int(String name, String s, boolean split) throws AltosConfigDataException {
+ String v = s;
+ if (split)
+ v = s.split("\\s+")[0];
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException ne) {
+ throw new AltosConfigDataException("Invalid %s \"%s\"", name, s);
+ }
+ }
+
+ public void set_radio_enable(int new_radio_enable) {
+ if (new_radio_enable >= 0) {
+ radio_enable_value.setSelected(new_radio_enable > 0);
+ radio_enable_value.setEnabled(true);
+ } else {
+ radio_enable_value.setSelected(true);
+ radio_enable_value.setVisible(radio_frequency() > 0);
+ radio_enable_value.setEnabled(false);
+ }
+ set_radio_enable_tool_tip();
+ }
+
+ public int radio_enable() {
+ if (radio_enable_value.isEnabled())
+ return radio_enable_value.isSelected() ? 1 : 0;
+ else
+ return -1;
+ }
+
+ public void set_telemetry_rate(int new_rate) {
+ rate_value.set_rate(new_rate);
+ }
+
+ public int telemetry_rate() {
+ return rate_value.rate();
+ }
+
+ public void set_callsign(String new_callsign) {
+ callsign_value.setVisible(new_callsign != null);
+ callsign_value.setText(new_callsign);
+ }
+
+ public String callsign() {
+ return callsign_value.getText();
+ }
+
+ int flight_log_max_limit;
+ int flight_log_max;
+
+ public String flight_log_max_label(int flight_log_max) {
+ if (flight_log_max_limit != 0) {
+ int nflight = flight_log_max_limit / flight_log_max;
+ String plural = nflight > 1 ? "s" : "";
+
+ return String.format("%d (%d flight%s)", flight_log_max, nflight, plural);
+ }
+ return String.format("%d", flight_log_max);
+ }
+
+ public void set_flight_log_max(int new_flight_log_max) {
+ flight_log_max_value.setSelectedItem(flight_log_max_label(new_flight_log_max));
+ flight_log_max = new_flight_log_max;
+ set_flight_log_max_tool_tip();
+ }
+
+ public void set_flight_log_max_enabled(boolean enable) {
+ flight_log_max_value.setEnabled(enable);
+ set_flight_log_max_tool_tip();
+ }
+
+ public int flight_log_max() throws AltosConfigDataException {
+ return parse_int("flight log max", flight_log_max_value.getSelectedItem().toString(), true);
+ }
+
+ public void set_flight_log_max_limit(int new_flight_log_max_limit) {
+ flight_log_max_limit = new_flight_log_max_limit;
+ flight_log_max_value.removeAllItems();
+ for (int i = 8; i >= 1; i--) {
+ int size = flight_log_max_limit / i;
+ flight_log_max_value.addItem(String.format("%d (%d flights)", size, i));
+ }
+ if (flight_log_max != 0)
+ set_flight_log_max(flight_log_max);
+ }
+
+ public void set_ignite_mode(int new_ignite_mode) { }
+ public int ignite_mode() { return -1; }
+
+
+ public void set_pad_orientation(int new_pad_orientation) { }
+ public int pad_orientation() { return -1; }
+
+ public void set_beep(int new_beep) { }
+
+ public int beep() { return -1; }
+
+ String[] tracker_motion_values() {
+ if (AltosConvert.imperial_units)
+ return tracker_motion_values_ft;
+ else
+ return tracker_motion_values_m;
+ }
+
+ void set_tracker_motion_values() {
+ String[] v = tracker_motion_values();
+ while (tracker_motion_value.getItemCount() > 0)
+ tracker_motion_value.removeItemAt(0);
+ for (int i = 0; i < v.length; i++)
+ tracker_motion_value.addItem(v[i]);
+ tracker_motion_value.setMaximumRowCount(v.length);
+ }
+
+ String get_tracker_motion_label() {
+ return String.format("Logging Trigger Motion (%s):", AltosConvert.height.parse_units());
+ }
+
+ void set_tracker_tool_tip() {
+ if (tracker_motion_value.isEnabled())
+ tracker_motion_value.setToolTipText("How far the device must move before logging");
+ else
+ tracker_motion_value.setToolTipText("This device doesn't disable logging when stationary");
+ if (tracker_interval_value.isEnabled())
+ tracker_interval_value.setToolTipText("How often to report GPS position");
+ else
+ tracker_interval_value.setToolTipText("This device can't configure interval");
+ }
+
+ public void set_tracker_motion(int tracker_motion) {
+ if (tracker_motion < 0) {
+ tracker_motion_value.setEnabled(false);
+ } else {
+ tracker_motion_value.setEnabled(true);
+ tracker_motion_value.setSelectedItem(AltosConvert.height.say(tracker_motion));
+ }
+ }
+
+ public int tracker_motion() throws AltosConfigDataException {
+ String str = tracker_motion_value.getSelectedItem().toString();
+ try {
+ return (int) (AltosConvert.height.parse_locale(str) + 0.5);
+ } catch (ParseException pe) {
+ throw new AltosConfigDataException("invalid tracker motion %s", str);
+ }
+ }
+
+ public void set_tracker_interval(int tracker_interval) {
+ if (tracker_interval< 0) {
+ tracker_interval_value.setEnabled(false);
+ } else {
+ tracker_interval_value.setEnabled(true);
+ tracker_interval_value.setSelectedItem(String.format("%d", tracker_interval));
+ }
+ }
+
+ public int tracker_interval() throws AltosConfigDataException {
+ return parse_int ("tracker interval", tracker_interval_value.getSelectedItem().toString(), false);
+ }
+
+ public void set_aprs_interval(int new_aprs_interval) {
+ String s;
+
+ if (new_aprs_interval <= 0)
+ s = "Disabled";
+ else
+ s = Integer.toString(new_aprs_interval);
+ aprs_interval_value.setSelectedItem(s);
+ aprs_interval_value.setVisible(new_aprs_interval >= 0);
+ set_aprs_interval_tool_tip();
+ }
+
+ public int aprs_interval() throws AltosConfigDataException {
+ String s = aprs_interval_value.getSelectedItem().toString();
+
+ if (s.equals("Disabled"))
+ return 0;
+ return parse_int("aprs interval", s, false);
+ }
+
+ public void set_aprs_ssid(int new_aprs_ssid) {
+ aprs_ssid_value.setSelectedItem(Math.max(0,new_aprs_ssid));
+ aprs_ssid_value.setVisible(new_aprs_ssid >= 0);
+ set_aprs_ssid_tool_tip();
+ }
+
+ public int aprs_ssid() throws AltosConfigDataException {
+ Integer i = (Integer) aprs_ssid_value.getSelectedItem();
+ return i;
+ }
+
+ public void set_aprs_format(int new_aprs_format) {
+ aprs_format_value.setVisible(new_aprs_format >= 0);
+ aprs_format_label.setVisible(new_aprs_format >= 0);
+
+ aprs_format_value.setSelectedIndex(Math.max(0,new_aprs_format));
+ set_aprs_format_tool_tip();
+ }
+
+ public int aprs_format() throws AltosConfigDataException {
+ return aprs_format_value.getSelectedIndex();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.*;
+import javax.swing.*;
+import java.io.*;
+import java.text.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandDisplayThread extends Thread {
+
+ Frame parent;
+ IdleThread idle_thread;
+ AltosVoice voice;
+ AltosFlightReader reader;
+ AltosState old_state, state;
+ AltosListenerState listener_state;
+ AltosFlightDisplay display;
+
+ synchronized void show_safely() {
+ final AltosState my_state = state;
+ final AltosListenerState my_listener_state = listener_state;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ display.show(my_state, my_listener_state);
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ void reading_error_internal() {
+ JOptionPane.showMessageDialog(parent,
+ String.format("Error reading from \"%s\"", reader.name),
+ "Telemetry Read Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ void reading_error_safely() {
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ reading_error_internal();
+ } catch (Exception ex) {
+ }
+ }
+ };
+ SwingUtilities.invokeLater(r);
+ }
+
+ class IdleThread extends Thread {
+
+ boolean started;
+ int report_interval;
+ long report_time;
+
+ public synchronized void report(boolean last) {
+ if (state == null)
+ return;
+
+ if (state.height() != AltosLib.MISSING) {
+ if (state.from_pad != null) {
+ voice.speak("Height %s, bearing %s %d, elevation %d, range %s, .\n",
+ AltosConvert.height.say(state.gps_height()),
+ state.from_pad.bearing_words(
+ AltosGreatCircle.BEARING_VOICE),
+ (int) (state.from_pad.bearing + 0.5),
+ (int) (state.elevation + 0.5),
+ AltosConvert.distance.say(state.range));
+ } else {
+ voice.speak("Height %s.\n",
+ AltosConvert.height.say(state.height()));
+ }
+ }
+ }
+
+ long now () {
+ return System.currentTimeMillis();
+ }
+
+ void set_report_time() {
+ report_time = now() + report_interval;
+ }
+
+ public void run () {
+ try {
+ for (;;) {
+ if (reader.has_monitor_battery()) {
+ listener_state.battery = reader.monitor_battery();
+ show_safely();
+ }
+ 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(boolean spoken) {
+ if (old_state != null && old_state.state() != state.state()) {
+ report_time = now();
+ this.notify();
+ } else if (spoken)
+ set_report_time();
+ }
+
+ public IdleThread() {
+ report_interval = 10000;
+ }
+ }
+
+ synchronized boolean tell() {
+ boolean ret = false;
+ 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;
+ boolean told;
+
+ idle_thread = new IdleThread();
+ idle_thread.start();
+
+ try {
+ for (;;) {
+ try {
+ state = reader.read();
+ if (state == null) {
+ listener_state.running = false;
+ break;
+ }
+ reader.update(state);
+ show_safely();
+ told = tell();
+ idle_thread.notice(told);
+ } catch (ParseException pp) {
+ System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage());
+ } catch (AltosCRCException ce) {
+ ++listener_state.crc_errors;
+ show_safely();
+ }
+ }
+ } catch (InterruptedException ee) {
+ interrupted = true;
+ } catch (IOException ie) {
+ reading_error_safely();
+ } finally {
+ if (!interrupted)
+ idle_thread.report(true);
+ reader.close(interrupted);
+ idle_thread.interrupt();
+ try {
+ idle_thread.join();
+ } catch (InterruptedException ie) {}
+ }
+ }
+
+ public TestStandDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) {
+ listener_state = new AltosListenerState();
+ parent = in_parent;
+ voice = in_voice;
+ display = in_display;
+ reader = in_reader;
+ display.reset();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.*;
+import java.util.concurrent.*;
+import java.util.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.ui.RefineryUtilities;
+
+public class TestStandGraphUI extends AltosUIFrame
+{
+ JTabbedPane pane;
+ AltosGraph graph;
+ AltosUIEnable enable;
+ AltosUIMap map;
+ AltosState state;
+ AltosFlightStats stats;
+ AltosGraphDataSet graphDataSet;
+ AltosFlightStatsTable statsTable;
+
+ void fill_map(AltosStateIterable states) {
+ for (AltosState state : states) {
+ if (state.gps != null && state.gps.locked && state.gps.nsat >= 4)
+ map.show(state, null);
+ }
+ }
+
+ private void close() {
+ setVisible(false);
+ dispose();
+ TestStand.subtract_window();
+ }
+
+ TestStandGraphUI(AltosStateIterable states, File file) throws InterruptedException, IOException {
+ super(file.getName());
+ state = null;
+
+ pane = new JTabbedPane();
+
+ enable = new AltosUIEnable();
+ stats = new AltosFlightStats(states);
+ graphDataSet = new AltosGraphDataSet(states);
+ graph = new AltosGraph(enable, stats, graphDataSet);
+ statsTable = new AltosFlightStatsTable(stats);
+
+ map = new AltosUIMap();
+
+ pane.add("Graph", graph.panel);
+ pane.add("Configure Graph", enable);
+ pane.add("Statistics", statsTable);
+ fill_map(states);
+ pane.add("Map", map);
+
+ setContentPane (pane);
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ close();
+ }
+ });
+
+ pack();
+
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+
+ TestStand.add_window();
+
+ setVisible(true);
+
+ if (state != null)
+ map.centre(state);
+
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.util.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandInfo extends AltosUIFlightTab {
+
+ JLabel cur, max;
+
+ abstract class Value extends AltosUIUnitsIndicator {
+ public abstract void show(AltosState state, AltosListenerState listener_state);
+
+ public Value (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text, 1, false, 2);
+ }
+ }
+
+ abstract class DualValue extends AltosUIUnitsIndicator {
+ public DualValue (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text, 2, false, 1);
+ }
+ }
+
+ abstract class ValueHold extends DualValue {
+ public void reset() {
+ super.reset();
+ }
+ public ValueHold (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text);
+ }
+ }
+
+ class Altitude extends ValueHold {
+ public double value(AltosState state, int i) {
+ if (i == 0)
+ return state.altitude();
+ else
+ return state.max_altitude();
+ }
+
+ public Altitude (Container container, int y) {
+ super (container, y, AltosConvert.height, "Altitude");
+ }
+ }
+
+ class AscentRate extends ValueHold {
+ public double value(AltosState state, int i) {
+ if (i == 0)
+ return state.gps_ascent_rate();
+ else
+ return state.max_gps_ascent_rate();
+ }
+ public AscentRate (Container container, int y) {
+ super (container, y, AltosConvert.speed, "Ascent Rate");
+ }
+ }
+
+ class GroundSpeed extends ValueHold {
+ public double value(AltosState state, int i) {
+ if (i == 0)
+ return state.gps_ground_speed();
+ else
+ return state.max_gps_ground_speed();
+ }
+ public GroundSpeed (Container container, int y) {
+ super (container, y, AltosConvert.speed, "Ground Speed");
+ }
+ }
+
+ class Course extends AltosUIIndicator {
+
+ public void show (AltosState state, AltosListenerState listener_state) {
+ double course = state.gps_course();
+ if (course == AltosLib.MISSING)
+ show("Missing", "Missing");
+ else
+ show( String.format("%3.0f°", course),
+ AltosConvert.bearing_to_words(
+ AltosConvert.BEARING_LONG,
+ course));
+ }
+ public Course (Container container, int y) {
+ super (container, y, "Course", 2, false, 1);
+ }
+ }
+
+ class Lat extends AltosUIIndicator {
+
+ 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);
+ }
+
+ public void show (AltosState state, AltosListenerState listener_state) {
+ if (state.gps != null && state.gps.connected && state.gps.lat != AltosLib.MISSING)
+ show(pos(state.gps.lat,"N", "S"));
+ else
+ show("Missing");
+ }
+ public Lat (Container container, int y) {
+ super (container, y, "Latitude", 1, false, 2);
+ }
+ }
+
+ class Lon extends AltosUIIndicator {
+
+ 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);
+ }
+
+ public void show (AltosState state, AltosListenerState listener_state) {
+ if (state.gps != null && state.gps.connected && state.gps.lon != AltosLib.MISSING)
+ show(pos(state.gps.lon,"E", "W"));
+ else
+ show("Missing");
+ }
+ public Lon (Container container, int y) {
+ super (container, y, "Longitude", 1, false, 2);
+ }
+ }
+
+ class GPSLocked extends AltosUIIndicator {
+
+ public void show (AltosState state, AltosListenerState listener_state) {
+ if (state == null || state.gps == null)
+ hide();
+ else {
+ int soln = state.gps.nsat;
+ int nsat = state.gps.cc_gps_sat != null ? state.gps.cc_gps_sat.length : 0;
+ show("%4d in solution", soln,
+ "%4d in view", nsat);
+ set_lights(state.gps.locked && soln >= 4);
+ }
+ }
+ public GPSLocked (Container container, int y) {
+ super (container, y, "GPS Locked", 2, true, 1);
+ }
+ }
+
+ public void font_size_changed(int font_size) {
+ cur.setFont(AltosUILib.label_font);
+ max.setFont(AltosUILib.label_font);
+ super.font_size_changed(font_size);
+ }
+
+ public void labels(Container container, int y) {
+ GridBagLayout layout = (GridBagLayout)(container.getLayout());
+ GridBagConstraints c;
+
+ cur = new JLabel("Current");
+ cur.setFont(AltosUILib.label_font);
+ c = new GridBagConstraints();
+ c.gridx = 2; c.gridy = y;
+ c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+ layout.setConstraints(cur, c);
+ add(cur);
+
+ max = new JLabel("Maximum");
+ max.setFont(AltosUILib.label_font);
+ c.gridx = 3; c.gridy = y;
+ layout.setConstraints(max, c);
+ add(max);
+ }
+
+ public String getName() {
+ return "Location";
+ }
+
+ public TestStandInfo() {
+ int y = 0;
+ labels(this, y++);
+ add(new Altitude(this, y++));
+ add(new GroundSpeed(this, y++));
+ add(new AscentRate(this, y++));
+ add(new Course(this, y++));
+ add(new Lat(this, y++));
+ add(new Lon(this, y++));
+ add(new GPSLocked(this, y++));
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandPreferences
+ extends AltosUIConfigure
+ implements DocumentListener
+{
+ AltosVoice voice;
+
+ public JTextField callsign_value;
+ public JComboBox<String> position_value;
+
+ /* DocumentListener interface methods */
+ public void insertUpdate(DocumentEvent e) {
+ changedUpdate(e);
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ changedUpdate(e);
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ if (callsign_value != null)
+ AltosUIPreferences.set_callsign(callsign_value.getText());
+ }
+
+ public void add_voice() {
+
+ /* Voice settings */
+ pane.add(new JLabel("Voice"), constraints(0, 1));
+
+ JRadioButton enable_voice = new JRadioButton("Enable", AltosUIPreferences.voice());
+ enable_voice.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ JRadioButton item = (JRadioButton) e.getSource();
+ boolean enabled = item.isSelected();
+ AltosUIPreferences.set_voice(enabled);
+ if (enabled)
+ voice.speak_always("Enable voice.");
+ else
+ voice.speak_always("Disable voice.");
+ }
+ });
+ pane.add(enable_voice, constraints(1, 1));
+ enable_voice.setToolTipText("Enable/Disable all audio in-flight announcements");
+
+ JButton test_voice = new JButton("Test Voice");
+ test_voice.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ voice.speak("That's one small step for man; one giant leap for mankind.");
+ }
+ });
+ pane.add(test_voice, constraints(2, 1));
+ test_voice.setToolTipText("Play a stock audio clip to check volume");
+ row++;
+ }
+
+ public void add_callsign() {
+ /* Callsign setting */
+ pane.add(new JLabel("Callsign"), constraints(0, 1));
+
+ callsign_value = new JTextField(AltosUIPreferences.callsign());
+ callsign_value.getDocument().addDocumentListener(this);
+ callsign_value.setToolTipText("Callsign sent in packet mode");
+ pane.add(callsign_value, constraints(1, 2, GridBagConstraints.BOTH));
+ row++;
+ }
+
+ public void add_bluetooth() {
+ JButton manage_bluetooth = new JButton("Manage Bluetooth");
+ manage_bluetooth.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ AltosBTManage.show(owner, AltosBTKnown.bt_known());
+ }
+ });
+ pane.add(manage_bluetooth, constraints(0, 2));
+ /* in the same row as add_frequencies, so don't bump row */
+ }
+
+ public void add_frequencies() {
+ JButton manage_frequencies = new JButton("Manage Frequencies");
+ manage_frequencies.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ AltosConfigFreqUI.show(owner);
+ }
+ });
+ manage_frequencies.setToolTipText("Configure which values are shown in frequency menus");
+ pane.add(manage_frequencies, constraints(2, 1));
+ row++;
+ }
+
+ public TestStandPreferences(JFrame owner, AltosVoice voice) {
+ super(owner, "TestStand Preferences", "Configure TestStand");
+
+ this.voice = voice;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.util.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandState extends AltosUIFlightTab {
+
+ JLabel cur, max;
+
+ abstract class Value extends AltosUIUnitsIndicator {
+ public Value (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text, 1, false, 2);
+ }
+ }
+
+ abstract class DualValue extends AltosUIUnitsIndicator {
+ public DualValue (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text, 2, false, 1);
+ }
+ }
+
+ abstract class ValueHold extends DualValue {
+ public ValueHold (Container container, int y, AltosUnits units, String text) {
+ super(container, y, units, text);
+ }
+ }
+
+ class Height extends ValueHold {
+ public double value(AltosState state, int i) {
+ if (i == 0)
+ return state.height();
+ else
+ return state.max_height();
+ }
+
+ public Height(Container container, int y) {
+ super(container, y, AltosConvert.height, "Height");
+ }
+ }
+
+ class Speed extends ValueHold {
+ public double value(AltosState state, int i) {
+ if (i == 0)
+ return state.gps_speed();
+ else
+ return state.max_gps_speed();
+ }
+
+ public Speed(Container container, int y) {
+ super(container, y, AltosConvert.speed, "Speed");
+ }
+ }
+
+ class Distance extends Value {
+ public double value(AltosState state, int i) {
+ if (state.from_pad != null)
+ return state.from_pad.distance;
+ else
+ return AltosLib.MISSING;
+ }
+
+ public Distance(Container container, int y) {
+ super(container, y, AltosConvert.distance, "Distance");
+ }
+ }
+
+ class Range extends Value {
+ public double value(AltosState state, int i) {
+ return state.range;
+ }
+ public Range (Container container, int y) {
+ super (container, y, AltosConvert.distance, "Range");
+ }
+ }
+
+ class Bearing extends AltosUIIndicator {
+ public void show (AltosState state, AltosListenerState listener_state) {
+ if (state.from_pad != null && state.from_pad.bearing != AltosLib.MISSING) {
+ show( String.format("%3.0f°", state.from_pad.bearing),
+ state.from_pad.bearing_words(
+ AltosGreatCircle.BEARING_LONG));
+ } else {
+ show("Missing", "Missing");
+ }
+ }
+ public Bearing (Container container, int y) {
+ super (container, y, "Bearing", 2, false, 1);
+ }
+ }
+
+ class Elevation extends AltosUIIndicator {
+ public void show (AltosState state, AltosListenerState listener_state) {
+ if (state.elevation == AltosLib.MISSING)
+ show("Missing");
+ else
+ show("%3.0f°", state.elevation);
+ }
+ public Elevation (Container container, int y) {
+ super (container, y, "Elevation", 1, false, 2);
+ }
+ }
+
+ class FirmwareVersion extends AltosUIIndicator {
+ public void show(AltosState state, AltosListenerState listener_state) {
+ if (state.firmware_version == null)
+ show("Missing");
+ else
+ show(state.firmware_version);
+ }
+
+ public FirmwareVersion(Container container, int y) {
+ super(container, y, "Firmware Version", 1, false, 2);
+ }
+ }
+
+ class FlightLogMax extends AltosUIIndicator {
+ public void show(AltosState state, AltosListenerState listener_state) {
+ int storage = state.flight_log_max;
+ if (storage == AltosLib.MISSING)
+ storage = state.log_space >> 10;
+ if (storage == AltosLib.MISSING)
+ show("Missing");
+ else
+ show(String.format("%dkB", storage));
+ }
+
+ public FlightLogMax(Container container, int y) {
+ super(container, y, "Flight Log Storage", 1, false, 2);
+ }
+ }
+
+ class BatteryVoltage extends AltosUIVoltageIndicator {
+ public double voltage(AltosState state) {
+ return state.battery_voltage;
+ }
+
+ public double good() {
+ return AltosLib.ao_battery_good;
+ }
+
+ public BatteryVoltage(Container container, int y) {
+ super(container, y, "Battery Voltage", 2);
+ }
+ }
+
+ class ReceiverBattery extends AltosUIVoltageIndicator {
+
+ public double voltage(AltosState state) { return AltosLib.MISSING; }
+
+ public double good() { return AltosLib.ao_battery_good; }
+
+ public boolean hide(AltosState state, AltosListenerState listener_state, int i) {
+ return value(state, listener_state, i) == AltosLib.MISSING;
+ }
+
+ public double value(AltosState state, AltosListenerState listener_state, int i) {
+ if (listener_state == null)
+ return AltosLib.MISSING;
+ return listener_state.battery;
+ }
+
+ public ReceiverBattery (AltosUIFlightTab container, int y) {
+ super(container, y, "Receiver Battery", 2);
+ }
+ }
+
+ public void labels(Container container, int y) {
+ GridBagLayout layout = (GridBagLayout)(container.getLayout());
+ GridBagConstraints c;
+
+ cur = new JLabel("Current");
+ cur.setFont(AltosUILib.label_font);
+ c = new GridBagConstraints();
+ c.gridx = 2; c.gridy = y;
+ c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+ layout.setConstraints(cur, c);
+ add(cur);
+
+ max = new JLabel("Maximum");
+ max.setFont(AltosUILib.label_font);
+ c.gridx = 3; c.gridy = y;
+ layout.setConstraints(max, c);
+ add(max);
+ }
+
+ public void font_size_changed(int font_size) {
+ cur.setFont(AltosUILib.label_font);
+ max.setFont(AltosUILib.label_font);
+ super.font_size_changed(font_size);
+ }
+
+ public String getName() {
+ return "Status";
+ }
+
+ public TestStandState() {
+ int y = 0;
+ labels(this, y++);
+ add(new Height(this, y++));
+ add(new Speed(this, y++));
+ add(new Distance(this, y++));
+ add(new Range(this, y++));
+ add(new Bearing(this, y++));
+ add(new Elevation(this, y++));
+ add(new FirmwareVersion(this, y++));
+ add(new FlightLogMax(this, y++));
+ add(new BatteryVoltage(this, y++));
+ add(new ReceiverBattery(this, y++));
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.*;
+import javax.swing.*;
+import org.altusmetrum.altoslib_11.*;
+import org.altusmetrum.altosuilib_11.*;
+
+public class TestStandStatus extends JComponent implements AltosFlightDisplay {
+ GridBagLayout layout;
+
+ public class Value {
+ JLabel label;
+ JTextField value;
+
+ void show(AltosState state, AltosListenerState listener_state) {}
+
+ void reset() {
+ value.setText("");
+ }
+
+ void set_font() {
+ label.setFont(AltosUILib.status_font);
+ value.setFont(AltosUILib.status_font);
+ }
+
+ void setVisible(boolean visible) {
+ label.setVisible(visible);
+ value.setVisible(visible);
+ }
+
+ public Value (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(AltosUILib.status_font);
+ label.setHorizontalAlignment(SwingConstants.CENTER);
+ c.gridx = x; c.gridy = 0;
+ layout.setConstraints(label, c);
+ add(label);
+
+ value = new JTextField("");
+ value.setEditable(false);
+ value.setFont(AltosUILib.status_font);
+ value.setHorizontalAlignment(SwingConstants.CENTER);
+ c.gridx = x; c.gridy = 1;
+ layout.setConstraints(value, c);
+ add(value);
+ }
+ }
+
+ class Call extends Value {
+ String call;
+
+ void show(AltosState state, AltosListenerState listener_state) {
+ if (state.callsign != call) {
+ value.setText(state.callsign);
+ call = state.callsign;
+ }
+ if (state.callsign == null)
+ setVisible(false);
+ else
+ setVisible(true);
+ }
+
+ public void reset() {
+ super.reset();
+ call = "";
+ }
+
+ public Call (GridBagLayout layout, int x) {
+ super (layout, x, "Callsign");
+ }
+ }
+
+ Call call;
+
+ class Serial extends Value {
+ int serial = -1;
+ void show(AltosState state, AltosListenerState listener_state) {
+ if (state.serial != serial) {
+ if (state.serial == AltosLib.MISSING)
+ value.setText("none");
+ else
+ value.setText(String.format("%d", state.serial));
+ serial = state.serial;
+ }
+ }
+
+ public void reset() {
+ super.reset();
+ serial = -1;
+ }
+
+ public Serial (GridBagLayout layout, int x) {
+ super (layout, x, "Serial");
+ }
+ }
+
+ Serial serial;
+
+ class Flight extends Value {
+
+ int last_flight = -1;
+
+ void show(AltosState state, AltosListenerState listener_state) {
+ if (state.flight != last_flight) {
+ if (state.flight == AltosLib.MISSING)
+ value.setText("none");
+ else
+ value.setText(String.format("%d", state.flight));
+ last_flight = state.flight;
+ }
+ }
+
+ public void reset() {
+ super.reset();
+ last_flight = -1;
+ }
+
+ public Flight (GridBagLayout layout, int x) {
+ super (layout, x, "Flight");
+ }
+ }
+
+ Flight flight;
+
+ class RSSI extends Value {
+ int rssi = 10000;
+
+ void show(AltosState state, AltosListenerState listener_state) {
+ int new_rssi = state.rssi();
+
+ if (new_rssi != rssi) {
+ value.setText(String.format("%d", new_rssi));
+ if (state.rssi == AltosLib.MISSING)
+ setVisible(false);
+ else
+ setVisible(true);
+ rssi = new_rssi;
+ }
+ }
+
+ public void reset() {
+ super.reset();
+ rssi = 10000;
+ }
+
+ public RSSI (GridBagLayout layout, int x) {
+ super (layout, x, "RSSI");
+ }
+ }
+
+ RSSI rssi;
+
+ class LastPacket extends Value {
+
+ long last_secs = -1;
+
+ void show(AltosState state, AltosListenerState listener_state) {
+ if (listener_state.running) {
+ long secs = (System.currentTimeMillis() - state.received_time + 500) / 1000;
+
+ if (secs != last_secs) {
+ value.setText(String.format("%d", secs));
+ last_secs = secs;
+ }
+ } else {
+ value.setText("done");
+ }
+ }
+
+ void reset() {
+ super.reset();
+ last_secs = -1;
+ }
+
+ void disable() {
+ value.setText("");
+ }
+
+ public LastPacket(GridBagLayout layout, int x) {
+ super (layout, x, "Age");
+ }
+ }
+
+ LastPacket last_packet;
+
+ public void disable_receive() {
+ last_packet.disable();
+ }
+
+ public void reset () {
+ call.reset();
+ serial.reset();
+ flight.reset();
+ rssi.reset();
+ last_packet.reset();
+ }
+
+ public void font_size_changed(int font_size) {
+ call.set_font();
+ serial.set_font();
+ flight.set_font();
+ rssi.set_font();
+ last_packet.set_font();
+ }
+
+ public void units_changed(boolean imperial_units) {
+ }
+
+ public void show (AltosState state, AltosListenerState listener_state) {
+ call.show(state, listener_state);
+ serial.show(state, listener_state);
+ flight.show(state, listener_state);
+ rssi.show(state, listener_state);
+ last_packet.show(state, listener_state);
+ if (!listener_state.running)
+ stop();
+ }
+
+ public int height() {
+ Dimension d = layout.preferredLayoutSize(this);
+ return d.height;
+ }
+
+ TestStandStatusUpdate status_update;
+ javax.swing.Timer timer;
+
+ public void start(TestStandStatusUpdate status_update) {
+ this.status_update = status_update;
+ timer = new javax.swing.Timer(100, status_update);
+ timer.start();
+ }
+
+ public void stop() {
+ if (timer != null) {
+ timer.stop();
+ timer = null;
+ }
+ }
+
+ public TestStandStatus() {
+ layout = new GridBagLayout();
+
+ setLayout(layout);
+
+ call = new Call(layout, 0);
+ serial = new Serial(layout, 1);
+ flight = new Flight(layout, 2);
+ rssi = new RSSI(layout, 4);
+ last_packet = new LastPacket(layout, 5);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2017 Bdale Garbee <bdale@gag.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.teststand;
+
+import java.awt.event.*;
+import org.altusmetrum.altoslib_11.*;
+
+public class TestStandStatusUpdate implements ActionListener {
+
+ public AltosState saved_state;
+ public AltosListenerState saved_listener_state;
+ TestStandStatus status;
+
+ public void actionPerformed (ActionEvent e) {
+ if (saved_state != null) {
+ if (saved_listener_state == null)
+ saved_listener_state = new AltosListenerState();
+ status.show(saved_state, saved_listener_state);
+ }
+ }
+
+ public TestStandStatusUpdate (TestStandStatus in_status) {
+ status = in_status;
+ }
+}
+
--- /dev/null
+[Desktop Entry]
+Type=Application
+Name=TestStand
+GenericName=TestStand monitor, download and analysis
+Comment=View and log data from TestStand tracking devices
+Icon=%icondir%/altusmetrum-teststand.svg
+Exec=%bindir%/teststand %F
+Terminal=false
+MimeType=application/vnd.altusmetrum.eeprom
+Categories=Education;Electronics;Science;
--- /dev/null
+#!/bin/sh
+me=`which "$0"`
+dir=`dirname "$me"`
+exec java -cp "$dir/*" -Djava.library.path="$dir" -jar "$dir"/telegps-fat.jar "$@"
--- /dev/null
+!addplugindir ../altosui/Instdrv/NSIS/Plugins
+!addincludedir ../altosui/Instdrv/NSIS/Includes
+!include x64.nsh
+!include java.nsh
+!include refresh-sh.nsh
+
+!define REG_NAME "TeleGPS"
+!define PROG_ID_TELEM "altusmetrum.telegps.telem.1"
+!define PROG_ID_EEPROM "altusmetrum.telegps.eeprom.1"
+!define FAT_NAME "telegps-fat.jar"
+!define WIN_APP_ICON "altusmetrum-telegps.ico"
+!define WIN_APP_EXE "altusmetrum-telegps.exe"
+!define WIN_TELEM_EXE "application-vnd.altusmetrum.telemetry.exe"
+!define WIN_EEPROM_EXE "application-vnd.altusmetrum.eeprom.exe"
+
+Name "${REG_NAME} Installer"
+
+; Default install directory
+InstallDir "$PROGRAMFILES\AltusMetrum"
+
+; Tell the installer where to re-install a new version
+InstallDirRegKey HKLM "Software\${REG_NAME}" "Install_Dir"
+
+LicenseText "GNU General Public License Version 2"
+LicenseData "../COPYING"
+
+; Need admin privs for Vista or Win7
+RequestExecutionLevel admin
+
+ShowInstDetails Show
+
+ComponentText "${REG_NAME} Software and Driver Installer"
+
+Function .onInit
+ DetailPrint "Checking host operating system"
+ ${If} ${RunningX64}
+ DetailPrint "Installer running on 64-bit host"
+ SetRegView 64
+ StrCpy $INSTDIR "$PROGRAMFILES64\AltusMetrum"
+ ${DisableX64FSRedirection}
+ ${EndIf}
+FunctionEnd
+
+Function un.onInit
+ DetailPrint "Checking host operating system"
+ ${If} ${RunningX64}
+ DetailPrint "Installer running on 64-bit host"
+ SetRegView 64
+ StrCpy $INSTDIR "$PROGRAMFILES64\AltusMetrum"
+ ${DisableX64FSRedirection}
+ ${EndIf}
+FunctionEnd
+
+; Pages to present
+
+Page license
+Page components
+Page directory
+Page instfiles
+
+UninstPage uninstConfirm
+UninstPage instfiles
+
+; And the stuff to install
+
+Section "Install Driver" InstDriver
+
+ InstDrv::InitDriverSetup /NOUNLOAD {4D36E96D-E325-11CE-BFC1-08002BE10318} AltusMetrumSerial
+ Pop $0
+ DetailPrint "InitDriverSetup: $0"
+ InstDrv::DeleteOemInfFiles /NOUNLOAD
+ InstDrv::CreateDevice /NOUNLOAD
+
+ SetOutPath $INSTDIR
+ File "../altusmetrum.inf"
+ File "../altusmetrum.cat"
+
+ ${DisableX64FSRedirection}
+ IfFileExists $WINDIR\System32\PnPutil.exe 0 nopnp
+ ${DisableX64FSRedirection}
+ nsExec::ExecToLog '"$WINDIR\System32\PnPutil.exe" -i -a "$INSTDIR\altusmetrum.inf"'
+ Goto done
+nopnp:
+ InstDrv::InstallDriver /NOUNLOAD "$INSTDIR\altusmetrum.inf"
+done:
+
+SectionEnd
+
+Section "${REG_NAME} Application"
+ Call DetectJRE
+
+ SetOutPath $INSTDIR
+
+ File "${FAT_NAME}"
+ File "altoslib_@ALTOSLIB_VERSION@.jar"
+ File "altosuilib_@ALTOSUILIB_VERSION@.jar"
+ File "cmudict04.jar"
+ File "cmulex.jar"
+ File "cmu_time_awb.jar"
+ File "cmutimelex.jar"
+ File "cmu_us_kal.jar"
+ File "en_us.jar"
+ File "freetts.jar"
+ File "jfreechart.jar"
+ File "jcommon.jar"
+ File "../icon/${WIN_APP_EXE}"
+
+ File "*.dll"
+
+ File "../icon/${WIN_APP_ICON}"
+
+ CreateShortCut "$SMPROGRAMS\${REG_NAME}.lnk" "$INSTDIR\${WIN_APP_EXE}" "" "$INSTDIR\${WIN_APP_ICON}"
+SectionEnd
+
+Section "${REG_NAME} Desktop Shortcut"
+ CreateShortCut "$DESKTOP\${REG_NAME}.lnk" "$INSTDIR\${WIN_APP_EXE}" "" "$INSTDIR\${WIN_APP_ICON}"
+SectionEnd
+
+Section "TeleGPS, TeleDongle and TeleBT Firmware"
+
+ SetOutPath $INSTDIR
+
+ File "../src/telegps-v1.0/telegps-v1.0-${VERSION}.ihx"
+ File "../src/teledongle-v0.2/teledongle-v0.2-${VERSION}.ihx"
+ File "../src/teledongle-v3.0/teledongle-v3.0-${VERSION}.ihx"
+ File "../src/telebt-v1.0/telebt-v1.0-${VERSION}.ihx"
+
+SectionEnd
+
+Section "Documentation"
+
+ SetOutPath $INSTDIR
+
+ File "../doc/telegps.pdf"
+ File "../doc/altos.pdf"
+ File "../doc/telemetry.pdf"
+SectionEnd
+
+Section "File Associations"
+
+ ${DisableX64FSRedirection}
+
+ SetOutPath $INSTDIR
+
+ File "../icon/${WIN_TELEM_EXE}"
+ File "../icon/${WIN_EEPROM_EXE}"
+
+ DeleteRegKey HKCR "${PROG_ID_TELEM}"
+ DeleteRegKey HKCR "${PROG_ID_EEPROM}"
+
+ DeleteRegKey HKCR ".eeprom\${PROG_ID_EEPROM}"
+ DeleteRegValue HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}"
+ DeleteRegKey HKCR ".telem\${PROG_ID_EEPROM}"
+ DeleteRegValue HKCR ".telem\OpenWithProgids" "${PROG_ID_EEPROM}"
+
+ ; .eeprom elements
+
+ WriteRegStr HKCR "${PROG_ID_EEPROM}" "" "Altus Metrum Log File"
+ WriteRegStr HKCR "${PROG_ID_EEPROM}" "FriendlyTypeName" "Altus Metrum Log File"
+ WriteRegStr HKCR "${PROG_ID_EEPROM}\CurVer" "" "${PROG_ID_EEPROM}"
+ WriteRegStr HKCR "${PROG_ID_EEPROM}\DefaultIcon" "" '"$INSTDIR\${WIN_EEPROM_EXE}",-101'
+ WriteRegExpandStr HKCR "${PROG_ID_EEPROM}\shell\open\command" "" '"$INSTDIR\${WIN_APP_EXE}" "%1"'
+
+ WriteRegStr HKCR ".eeprom" "" "${PROG_ID_EEPROM}"
+ WriteRegStr HKCR ".eeprom" "PerceivedType" "Altus Metrum Log File"
+ WriteRegStr HKCR ".eeprom" "Content Type" "application/vnd.altusmetrum.eeprom"
+
+ WriteRegStr HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}" ""
+ WriteRegStr HKCR ".eeprom\${PROG_ID_EEPROM}" "" "${REG_NAME}"
+
+ ; .telem elements
+
+ WriteRegStr HKCR "${PROG_ID_TELEM}" "" "Altus Metrum Telemetry File"
+ WriteRegStr HKCR "${PROG_ID_TELEM}" "FriendlyTypeName" "Altus Metrum Telemetry File"
+ WriteRegStr HKCR "${PROG_ID_TELEM}\CurVer" "" "${PROG_ID_TELEM}"
+ WriteRegStr HKCR "${PROG_ID_TELEM}\DefaultIcon" "" '"$INSTDIR\${WIN_TELEM_EXE}",-101'
+ WriteRegExpandStr HKCR "${PROG_ID_TELEM}\shell\open\command" "" '"$INSTDIR\${WIN_APP_EXE}" "%1"'
+
+ WriteRegStr HKCR ".telem" "" "${PROG_ID_TELEM}"
+ WriteRegStr HKCR ".telem" "PerceivedType" "Altus Metrum Telemetry File"
+ WriteRegStr HKCR ".telem" "Content Type" "application/vnd.altusmetrum.telemetry"
+
+ WriteRegStr HKCR ".telem\OpenWithProgids" "${PROG_ID_TELEM}" ""
+ WriteRegStr HKCR ".telem\${PROG_ID_TELEM}" "" "${REG_NAME}"
+
+ Call RefreshShellIcons
+SectionEnd
+
+Section "Uninstaller"
+
+ ; Deal with the uninstaller
+
+ ${DisableX64FSRedirection}
+ SetOutPath $INSTDIR
+
+ ; Write the install path to the registry
+ WriteRegStr HKLM "SOFTWARE\${REG_NAME}" "Install_Dir" "$INSTDIR"
+
+ ; Write the uninstall keys for windows
+ WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "DisplayName" "${REG_NAME}"
+ WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "UninstallString" '"$INSTDIR\uninstall-${REG_NAME}.exe"'
+ WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "NoModify" "1"
+ WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}" "NoRepair" "1"
+
+ WriteUninstaller "uninstall-${REG_NAME}.exe"
+SectionEnd
+
+Section "Uninstall"
+
+ ${DisableX64FSRedirection}
+
+ DeleteRegKey HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${REG_NAME}"
+ DeleteRegKey HKLM "SOFTWARE\${REG_NAME}"
+
+ DetailPrint "Delete uninstall reg entries"
+
+ DeleteRegKey HKCR "${PROG_ID_EEPROM}"
+ DeleteRegKey HKCR "${PROG_ID_TELEM}"
+
+ DeleteRegKey HKCR ".eeprom\${PROG_ID_EEPROM}"
+ DeleteRegValue HKCR ".eeprom\OpenWithProgids" "${PROG_ID_EEPROM}"
+
+ DeleteRegKey HKCR ".telem\${PROG_ID_TELEM}"
+ DeleteRegValue HKCR ".telem\OpenWithProgids" "${PROG_ID_TELEM}"
+
+ DetailPrint "Delete file association reg entries"
+
+ Delete "$INSTDIR\${FAT_NAME}"
+ Delete "$INSTDIR\uninstall-${REG_NAME}.exe"
+
+ Delete "$INSTDIR\${WIN_APP_ICON}"
+ Delete "$INSTDIR\${WIN_APP_EXE}"
+
+ ; Remove shortcuts, if any
+ Delete "$SMPROGRAMS\${REG_NAME}.lnk"
+ Delete "$DESKTOP\${REG_NAME}.lnk"
+
+ Call un.RefreshShellIcons
+SectionEnd
--- /dev/null
+.\"
+.\" Copyright © 2017 Bdale Garbee <bdale@gag.com>
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 2 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+.\" General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License along
+.\" with this program; if not, write to the Free Software Foundation, Inc.,
+.\" 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+.\"
+.\"
+.TH TESTSTAND 1 "teststand" ""
+.SH NAME
+teststand \- Rocket motor test stand control and data analysis
+.SH SYNOPSIS
+.B "teststand"
+.SH DESCRIPTION
+.I teststand
+connects to a TeleDongle or TeleBT device through a USB serial interface.
+It provides a menu-oriented
+user interface to control a rocket motor test stand, colleting and helping
+analyze data about each test.
+.SH FILES
+All data log files are recorded into a user-specified directory
+(default ~/AltusMetrum). Files are named using the current date, the serial
+number of the reporting device, the test number recorded in the data
+and the suffix '.eeprom'.
+.SH AUTHOR
+Bdale Garbee