diff options
Diffstat (limited to 'altosui')
107 files changed, 19933 insertions, 0 deletions
diff --git a/altosui/.gitignore b/altosui/.gitignore new file mode 100644 index 00000000..6d2d8c23 --- /dev/null +++ b/altosui/.gitignore @@ -0,0 +1,22 @@ +windows/ +linux/ +macosx/ +fat/ +Manifest.txt +Manifest-fat.txt +AltosVersion.java +Info.plist +libaltosJNI +classes +altosui +altosui-test +altosui-jdb +classaltosui.stamp +Altos-Linux-*.tar.bz2 +Altos-Mac-*.zip +Altos-Windows-*.exe +*.dll +*.dylib +*.so +*.jar +*.class diff --git a/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml b/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml new file mode 100644 index 00000000..18e00fe4 --- /dev/null +++ b/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml @@ -0,0 +1 @@ +<pkg-contents spec="1.12"><f n="AltosUI.app" o="keithp" g="keithp" p="16877" pt="/Users/keithp/altos/ao-tools/altosui/AltosUI.app" m="false" t="file"><f n="Contents" o="keithp" g="keithp" p="16877"><f n="Info.plist" o="keithp" g="keithp" p="33188"/><f n="MacOS" o="keithp" g="keithp" p="16877"><f n="JavaApplicationStub" o="keithp" g="keithp" p="33133"/></f><f n="PkgInfo" o="keithp" g="keithp" p="33188"/><f n="Resources" o="keithp" g="keithp" p="16877"><f n="AltosUIIcon.icns" o="keithp" g="keithp" p="33188"/><f n="Java" o="keithp" g="keithp" p="16877"/></f></f></f></pkg-contents>
\ No newline at end of file diff --git a/altosui/AltOS Package Configuration.pmdoc/01altosui.xml b/altosui/AltOS Package Configuration.pmdoc/01altosui.xml new file mode 100644 index 00000000..6170931b --- /dev/null +++ b/altosui/AltOS Package Configuration.pmdoc/01altosui.xml @@ -0,0 +1 @@ +<pkgref spec="1.12" uuid="C5762664-2F26-4536-94C4-56F0FBC08D1A"><config><identifier>org.altusmetrum.altosUi.AltosUI.pkg</identifier><version>0.7</version><description></description><post-install type="none"/><installFrom relative="true" mod="true">AltosUI.app</installFrom><installTo mod="true" relocatable="true">/Applications/AltosUI.app</installTo><flags><followSymbolicLinks/></flags><packageStore type="internal"></packageStore><mod>installTo.path</mod><mod>installFrom.isRelativeType</mod><mod>version</mod><mod>parent</mod><mod>requireAuthorization</mod><mod>installTo</mod></config><contents><file-list>01altosui-contents.xml</file-list><filter>/CVS$</filter><filter>/\.svn$</filter><filter>/\.cvsignore$</filter><filter>/\.cvspass$</filter><filter>/\.DS_Store$</filter></contents></pkgref>
\ No newline at end of file diff --git a/altosui/AltOS Package Configuration.pmdoc/index.xml b/altosui/AltOS Package Configuration.pmdoc/index.xml new file mode 100644 index 00000000..fabe54a6 --- /dev/null +++ b/altosui/AltOS Package Configuration.pmdoc/index.xml @@ -0,0 +1 @@ +<pkmkdoc spec="1.12"><properties><title>AltOS UI</title><build>/Users/keithp/altos/ao-tools/altosui/AltosUI.pkg</build><organization>org.altusmetrum</organization><userSees ui="both"/><min-target os="3"/><domain system="true" user="true"/></properties><distribution><versions min-spec="1.000000"/><scripts></scripts></distribution><description>Install AltOS User Interface</description><contents><choice title="AltosUI" id="choice0" starts_selected="true" starts_enabled="true" starts_hidden="false"><pkgref id="org.altusmetrum.altosUi.AltosUI.pkg"/></choice></contents><resources bg-scale="tofit" bg-align="center"><locale lang="en"><resource relative="true" mod="true" type="background">altusmetrum.jpg</resource></locale></resources><flags/><item type="file">01altosui.xml</item><mod>properties.anywhereDomain</mod><mod>properties.title</mod><mod>properties.customizeOption</mod><mod>description</mod><mod>properties.userDomain</mod><mod>properties.systemDomain</mod></pkmkdoc>
\ No newline at end of file diff --git a/altosui/Altos.java b/altosui/Altos.java new file mode 100644 index 00000000..cd17a93e --- /dev/null +++ b/altosui/Altos.java @@ -0,0 +1,97 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.util.*; +import java.text.*; +import java.nio.charset.Charset; + +import libaltosJNI.*; + +import org.altusmetrum.AltosLib.*; + +public class Altos extends AltosLib { + + static final int tab_elt_pad = 5; + + static Font label_font; + static Font value_font; + static Font status_font; + static Font table_label_font; + static Font table_value_font; + + final static int font_size_small = 1; + final static int font_size_medium = 2; + final static int font_size_large = 3; + + static void set_fonts(int size) { + int brief_size; + int table_size; + int status_size; + + switch (size) { + case font_size_small: + brief_size = 16; + status_size = 18; + table_size = 11; + break; + default: + case font_size_medium: + brief_size = 22; + status_size = 24; + table_size = 14; + break; + case font_size_large: + brief_size = 26; + status_size = 30; + table_size = 17; + break; + } + label_font = new Font("Dialog", Font.PLAIN, brief_size); + value_font = new Font("Monospaced", Font.PLAIN, brief_size); + status_font = new Font("SansSerif", Font.BOLD, status_size); + table_label_font = new Font("SansSerif", Font.PLAIN, table_size); + table_value_font = new Font("Monospaced", Font.PLAIN, table_size); + } + + static final int text_width = 20; + + static public boolean initialized = false; + static public boolean loaded_library = false; + + public static boolean load_library() { + if (!initialized) { + try { + System.loadLibrary("altos"); + libaltos.altos_init(); + loaded_library = true; + } catch (UnsatisfiedLinkError e) { + try { + System.loadLibrary("altos64"); + libaltos.altos_init(); + loaded_library = true; + } catch (UnsatisfiedLinkError e2) { + loaded_library = false; + } + } + initialized = true; + } + return loaded_library; + } +} diff --git a/altosui/AltosAscent.java b/altosui/AltosAscent.java new file mode 100644 index 00000000..a158eb21 --- /dev/null +++ b/altosui/AltosAscent.java @@ -0,0 +1,406 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosAscent extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + JLabel cur, max; + + public class AscentStatus { + JLabel label; + JTextField value; + AltosLights lights; + + void show() { + value.setVisible(true); + lights.setVisible(true); + label.setVisible(true); + } + + void hide() { + value.setVisible(false); + lights.setVisible(false); + label.setVisible(false); + } + + void show(AltosState state, int crc_errors) {} + void reset() { + value.setText(""); + lights.set(false); + } + + void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + public AscentStatus (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + lights = new AltosLights(); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(lights, c); + add(lights); + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.gridwidth = 2; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + } + } + + public class AscentValue { + JLabel label; + JTextField value; + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + + void show() { + label.setVisible(true); + value.setVisible(true); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + } + void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + public AscentValue (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 2; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + } + } + + public class AscentValueHold { + JLabel label; + JTextField value; + JTextField max_value; + double max; + + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + max_value.setText(""); + max = AltosRecord.MISSING; + } + + void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + max_value.setFont(Altos.value_font); + } + + void show(AltosUnits units, double v) { + if (v == AltosRecord.MISSING) { + value.setText("Missing"); + max_value.setText("Missing"); + } else { + value.setText(units.show(8, v)); + if (v > max || max == AltosRecord.MISSING) { + max_value.setText(units.show(8, v)); + max = v; + } + } + } + public AscentValueHold (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + max_value = new JTextField(Altos.text_width); + max_value.setFont(Altos.value_font); + max_value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 3; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(max_value, c); + add(max_value); + } + } + + + class Height extends AscentValueHold { + void show (AltosState state, int crc_errors) { + show(AltosConvert.height, state.height); + } + public Height (GridBagLayout layout, int y) { + super (layout, y, "Height"); + } + } + + Height height; + + class Speed extends AscentValueHold { + void show (AltosState state, int crc_errors) { + double speed = state.speed; + if (!state.ascent) + speed = state.baro_speed; + show(AltosConvert.speed, speed); + } + public Speed (GridBagLayout layout, int y) { + super (layout, y, "Speed"); + } + } + + Speed speed; + + class Accel extends AscentValueHold { + void show (AltosState state, int crc_errors) { + show(AltosConvert.accel, state.acceleration); + } + public Accel (GridBagLayout layout, int y) { + super (layout, y, "Acceleration"); + } + } + + Accel accel; + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %4d° %9.6f", h, deg, min); + } + + class Apogee extends AscentStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.drogue_sense)); + lights.set(state.drogue_sense > 3.2); + } + public Apogee (GridBagLayout layout, int y) { + super(layout, y, "Apogee Igniter Voltage"); + } + } + + Apogee apogee; + + class Main extends AscentStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.main_sense)); + lights.set(state.main_sense > 3.2); + } + public Main (GridBagLayout layout, int y) { + super(layout, y, "Main Igniter Voltage"); + } + } + + Main main; + + class Lat extends AscentValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.gps != null) + value.setText(pos(state.gps.lat,"N", "S")); + else + value.setText("???"); + } + public Lat (GridBagLayout layout, int y) { + super (layout, y, "Latitude"); + } + } + + Lat lat; + + class Lon extends AscentValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.gps != null) + value.setText(pos(state.gps.lon,"E", "W")); + else + value.setText("???"); + } + public Lon (GridBagLayout layout, int y) { + super (layout, y, "Longitude"); + } + } + + Lon lon; + + public void reset() { + lat.reset(); + lon.reset(); + main.reset(); + apogee.reset(); + height.reset(); + speed.reset(); + accel.reset(); + } + + public void set_font() { + cur.setFont(Altos.label_font); + max.setFont(Altos.label_font); + lat.set_font(); + lon.set_font(); + main.set_font(); + apogee.set_font(); + height.set_font(); + speed.set_font(); + accel.set_font(); + } + + public void show(AltosState state, int crc_errors) { + if (state.gps != null && state.gps.connected) { + lat.show(state, crc_errors); + lon.show(state, crc_errors); + } else { + lat.hide(); + lon.hide(); + } + height.show(state, crc_errors); + if (state.main_sense != AltosRecord.MISSING) + main.show(state, crc_errors); + else + main.hide(); + if (state.drogue_sense != AltosRecord.MISSING) + apogee.show(state, crc_errors); + else + apogee.hide(); + speed.show(state, crc_errors); + accel.show(state, crc_errors); + } + + public void labels(GridBagLayout layout, int y) { + GridBagConstraints c; + + cur = new JLabel("Current"); + cur.setFont(Altos.label_font); + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + layout.setConstraints(cur, c); + add(cur); + + max = new JLabel("Maximum"); + max.setFont(Altos.label_font); + c.gridx = 3; c.gridy = y; + layout.setConstraints(max, c); + add(max); + } + + public AltosAscent() { + layout = new GridBagLayout(); + + setLayout(layout); + + /* Elements in ascent display: + * + * lat + * lon + * height + */ + labels(layout, 0); + height = new Height(layout, 1); + speed = new Speed(layout, 2); + accel = new Accel(layout, 3); + lat = new Lat(layout, 4); + lon = new Lon(layout, 5); + apogee = new Apogee(layout, 6); + main = new Main(layout, 7); + } +} diff --git a/altosui/AltosBTDevice.java b/altosui/AltosBTDevice.java new file mode 100644 index 00000000..5e353fdd --- /dev/null +++ b/altosui/AltosBTDevice.java @@ -0,0 +1,123 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; + +public class AltosBTDevice extends altos_bt_device implements AltosDevice { + + public String getProductName() { + String name = getName(); + if (name == null) + return "Altus Metrum"; + int dash = name.lastIndexOf("-"); + if (dash < 0) + return name; + return name.substring(0,dash); + } + + public int getProduct() { + if (Altos.bt_product_telebt.equals(getProductName())) + return Altos.product_telebt; + return 0; + } + + public String getPath() { + return getAddr(); + } + + public String getErrorString() { + altos_error error = new altos_error(); + + libaltos.altos_get_last_error(error); + return String.format("%s (%d)", error.getString(), error.getCode()); + } + + public int getSerial() { + String name = getName(); + if (name == null) + return 0; + int dash = name.lastIndexOf("-"); + if (dash < 0 || dash >= name.length()) + return 0; + String sn = name.substring(dash + 1, name.length()); + try { + return Integer.parseInt(sn); + } catch (NumberFormatException ne) { + return 0; + } + } + + public String toString() { + return String.format("%-20.20s %4d %s", + getProductName(), getSerial(), getAddr()); + } + + public String toShortString() { + return String.format("%s %d %s", + getProductName(), getSerial(), getAddr()); + + } + + public SWIGTYPE_p_altos_file open() { + return libaltos.altos_bt_open(this); + } + + private boolean isAltusMetrum() { + if (getName().startsWith(Altos.bt_product_telebt)) + return true; + return false; + } + + public boolean matchProduct(int want_product) { + +// if (!isAltusMetrum()) +// return false; + + if (want_product == Altos.product_any) + return true; + + if (want_product == Altos.product_basestation) + return matchProduct(Altos.product_telebt); + + if (want_product == getProduct()) + return true; + + return false; + } + + public boolean equals(Object o) { + if (!(o instanceof AltosBTDevice)) + return false; + AltosBTDevice other = (AltosBTDevice) o; + return getName().equals(other.getName()) && getAddr().equals(other.getAddr()); + } + + public int hashCode() { + return getName().hashCode() ^ getAddr().hashCode(); + } + + public AltosBTDevice(String name, String addr) { + Altos.load_library(); + libaltos.altos_bt_fill_in(name, addr,this); + } + + public AltosBTDevice() { + } +}
\ No newline at end of file diff --git a/altosui/AltosBTDeviceIterator.java b/altosui/AltosBTDeviceIterator.java new file mode 100644 index 00000000..58ed86d5 --- /dev/null +++ b/altosui/AltosBTDeviceIterator.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; + +public class AltosBTDeviceIterator implements Iterator<AltosBTDevice> { + AltosBTDevice current; + boolean done; + SWIGTYPE_p_altos_bt_list list; + + public boolean hasNext() { + if (list == null) + return false; + if (current != null) + return true; + if (done) + return false; + current = new AltosBTDevice(); + while (libaltos.altos_bt_list_next(list, current) != 0) { +// if (current.matchProduct(product)) + return true; + } + current = null; + done = true; + return false; + } + + public AltosBTDevice next() { + if (hasNext()) { + AltosBTDevice next = current; + current = null; + return next; + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public AltosBTDeviceIterator(int inquiry_time) { + done = false; + current = null; + list = libaltos.altos_bt_list_start(inquiry_time); + } +} diff --git a/altosui/AltosBTKnown.java b/altosui/AltosBTKnown.java new file mode 100644 index 00000000..6a8e53cb --- /dev/null +++ b/altosui/AltosBTKnown.java @@ -0,0 +1,103 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; +import java.util.prefs.*; + +public class AltosBTKnown implements Iterable<AltosBTDevice> { + LinkedList<AltosBTDevice> devices = new LinkedList<AltosBTDevice>(); + Preferences bt_pref = AltosUIPreferences.bt_devices(); + + private String get_address(String name) { + return bt_pref.get(name, ""); + } + + private void set_address(String name, String addr) { + bt_pref.put(name, addr); + } + + private void remove(String name) { + bt_pref.remove(name); + } + + private void load() { + try { + String[] names = bt_pref.keys(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + String addr = get_address(name); + devices.add(new AltosBTDevice(name, addr)); + } + } catch (BackingStoreException be) { + } catch (IllegalStateException ie) { + } + } + + public Iterator<AltosBTDevice> iterator() { + return devices.iterator(); + } + + private void flush() { + AltosUIPreferences.flush_preferences(); + } + + public void set(Iterable<AltosBTDevice> new_devices) { + for (AltosBTDevice old : devices) { + boolean found = false; + for (AltosBTDevice new_device : new_devices) { + if (new_device.equals(old)) { + found = true; + break; + } + } + if (!found) + remove(old.getName()); + } + devices = new LinkedList<AltosBTDevice>(); + for (AltosBTDevice new_device : new_devices) { + devices.add(new_device); + set_address(new_device.getName(), new_device.getAddr()); + } + flush(); + } + + public List<AltosDevice> list(int product) { + LinkedList<AltosDevice> list = new LinkedList<AltosDevice>(); + for (AltosBTDevice device : devices) { + if (device.matchProduct(product)) + list.add(device); + } + return list; + } + + public AltosBTKnown() { + devices = new LinkedList<AltosBTDevice>(); + bt_pref = AltosUIPreferences.bt_devices(); + load(); + } + + static AltosBTKnown known; + + static public AltosBTKnown bt_known() { + if (known == null) + known = new AltosBTKnown(); + return known; + } +} diff --git a/altosui/AltosBTManage.java b/altosui/AltosBTManage.java new file mode 100644 index 00000000..aeb964bb --- /dev/null +++ b/altosui/AltosBTManage.java @@ -0,0 +1,359 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import javax.swing.plaf.basic.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosBTManage extends AltosDialog implements ActionListener, Iterable<AltosBTDevice> { + LinkedBlockingQueue<AltosBTDevice> found_devices; + Frame frame; + LinkedList<ActionListener> listeners; + AltosBTKnown bt_known; + + class DeviceList extends JList implements Iterable<AltosBTDevice> { + LinkedList<AltosBTDevice> devices; + DefaultListModel list_model; + + public void add (AltosBTDevice device) { + if (!devices.contains(device)) { + devices.add(device); + list_model.addElement(device); + } + } + + public void remove (AltosBTDevice device) { + if (devices.contains(device)) { + devices.remove(device); + list_model.removeElement(device); + } + } + + public boolean contains(AltosBTDevice device) { + return devices.contains(device); + } + + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + + public Iterator<AltosBTDevice> iterator() { + return devices.iterator(); + } + + public java.util.List<AltosBTDevice> selected_list() { + java.util.LinkedList<AltosBTDevice> l = new java.util.LinkedList<AltosBTDevice>(); + Object[] a = getSelectedValues(); + for (int i = 0; i < a.length; i++) + l.add((AltosBTDevice)a[i]); + return l; + } + + public DeviceList() { + devices = new LinkedList<AltosBTDevice>(); + list_model = new DefaultListModel(); + setModel(list_model); + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + setLayoutOrientation(JList.HORIZONTAL_WRAP); + setVisibleRowCount(-1); + } + } + + DeviceList visible_devices; + + DeviceList known_devices; + Thread bt_thread; + + public Iterator<AltosBTDevice> iterator() { + return known_devices.iterator(); + } + + public void commit() { + bt_known.set(this); + } + + public void add_known() { + for (AltosBTDevice device : visible_devices.selected_list()) { + known_devices.add(device); + visible_devices.remove(device); + } + } + + public void remove_known() { + for (AltosBTDevice device : known_devices.selected_list()) { + known_devices.remove(device); + visible_devices.add(device); + } + } + + public void addActionListener(ActionListener l) { + listeners.add(l); + } + + private void forwardAction(ActionEvent e) { + for (ActionListener l : listeners) + l.actionPerformed(e); + } + + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ("ok".equals(command)) { + bt_thread.interrupt(); + commit(); + setVisible(false); + forwardAction(e); + } else if ("cancel".equals(command)) { + bt_thread.interrupt(); + setVisible(false); + forwardAction(e); + } else if ("select".equals(command)) { + add_known(); + } else if ("deselect".equals(command)) { + remove_known(); + } + } + + public void got_visible_device() { + while (!found_devices.isEmpty()) { + AltosBTDevice device = found_devices.remove(); + if (!known_devices.contains(device)) + visible_devices.add(device); + } + } + + class BTGetVisibleDevices implements Runnable { + public void run () { + for (;;) + for (int time = 1; time <= 8; time <<= 1) { + AltosBTDeviceIterator i = new AltosBTDeviceIterator(time); + AltosBTDevice device; + + if (Thread.interrupted()) + return; + try { + while ((device = i.next()) != null) { + Runnable r; + + if (Thread.interrupted()) + return; + found_devices.add(device); + r = new Runnable() { + public void run() { + got_visible_device(); + } + }; + SwingUtilities.invokeLater(r); + } + } catch (Exception e) { + System.out.printf("uh-oh, exception %s\n", e.toString()); + } + } + } + } + + public static void show(Component frameComp, AltosBTKnown known) { + Frame frame = JOptionPane.getFrameForComponent(frameComp); + AltosBTManage dialog; + + dialog = new AltosBTManage(frame, known); + dialog.setVisible(true); + } + + public AltosBTManage(Frame in_frame, AltosBTKnown in_known) { + super(in_frame, "Manage Bluetooth Devices", true); + + frame = in_frame; + bt_known = in_known; + BTGetVisibleDevices get_visible_devices = new BTGetVisibleDevices(); + bt_thread = new Thread(get_visible_devices); + bt_thread.start(); + + listeners = new LinkedList<ActionListener>(); + + found_devices = new LinkedBlockingQueue<AltosBTDevice>(); + + Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(4,4,4,4); + + /* + * Known devices label and list + */ + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(new JLabel("Known Devices"), c); + + known_devices = new DeviceList(); + for (AltosBTDevice device : bt_known) + known_devices.add(device); + + JScrollPane known_list_scroller = new JScrollPane(known_devices); + known_list_scroller.setPreferredSize(new Dimension(400, 80)); + known_list_scroller.setAlignmentX(LEFT_ALIGNMENT); + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 2; + c.weightx = 1; + c.weighty = 1; + pane.add(known_list_scroller, c); + + /* + * Visible devices label and list + */ + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 2; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + + pane.add(new JLabel("Visible Devices"), c); + + visible_devices = new DeviceList(); + JScrollPane visible_list_scroller = new JScrollPane(visible_devices); + visible_list_scroller.setPreferredSize(new Dimension(400, 80)); + visible_list_scroller.setAlignmentX(LEFT_ALIGNMENT); + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + c.gridx = 2; + c.gridy = 1; + c.gridheight = 2; + c.gridwidth = 1; + c.weightx = 1; + c.weighty = 1; + pane.add(visible_list_scroller, c); + + /* + * Arrows between the two lists + */ + BasicArrowButton select_arrow = new BasicArrowButton(SwingConstants.WEST); + select_arrow.setActionCommand("select"); + select_arrow.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.SOUTH; + c.gridx = 1; + c.gridy = 1; + c.gridheight = 1; + c.gridwidth = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(select_arrow, c); + + BasicArrowButton deselect_arrow = new BasicArrowButton(SwingConstants.EAST); + deselect_arrow.setActionCommand("deselect"); + deselect_arrow.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.NORTH; + c.gridx = 1; + c.gridy = 2; + c.gridheight = 1; + c.gridwidth = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(deselect_arrow, c); + + JButton cancel_button = new JButton("Cancel"); + cancel_button.setActionCommand("cancel"); + cancel_button.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; + c.gridy = 3; + c.gridheight = 1; + c.gridwidth = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(cancel_button, c); + + JButton ok_button = new JButton("OK"); + ok_button.setActionCommand("ok"); + ok_button.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 2; + c.gridy = 3; + c.gridheight = 1; + c.gridwidth = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(ok_button, c); + + getRootPane().setDefaultButton(ok_button); + + pack(); + setLocationRelativeTo(frame); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + bt_thread.interrupt(); + setVisible(false); + } + }); + } +} diff --git a/altosui/AltosCSV.java b/altosui/AltosCSV.java new file mode 100644 index 00000000..c876d9ca --- /dev/null +++ b/altosui/AltosCSV.java @@ -0,0 +1,338 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + +public class AltosCSV implements AltosWriter { + File name; + PrintStream out; + boolean header_written; + boolean seen_boost; + int boost_tick; + LinkedList<AltosRecord> pad_records; + AltosState state; + + static final int ALTOS_CSV_VERSION = 5; + + /* Version 4 format: + * + * General info + * version number + * serial number + * flight number + * callsign + * time (seconds since boost) + * clock (tick count / 100) + * rssi + * link quality + * + * Flight status + * state + * state name + * + * Basic sensors + * acceleration (m/s²) + * pressure (mBar) + * altitude (m) + * height (m) + * accelerometer speed (m/s) + * barometer speed (m/s) + * temp (°C) + * battery (V) + * drogue (V) + * main (V) + * + * Advanced sensors (if available) + * accel_x (m/s²) + * accel_y (m/s²) + * accel_z (m/s²) + * gyro_x (d/s) + * gyro_y (d/s) + * gyro_z (d/s) + * mag_x (g) + * mag_y (g) + * mag_z (g) + * + * GPS data (if available) + * connected (1/0) + * locked (1/0) + * nsat (used for solution) + * latitude (°) + * longitude (°) + * altitude (m) + * year (e.g. 2010) + * month (1-12) + * day (1-31) + * hour (0-23) + * minute (0-59) + * second (0-59) + * from_pad_dist (m) + * from_pad_azimuth (deg true) + * from_pad_range (m) + * from_pad_elevation (deg from horizon) + * hdop + * + * GPS Sat data + * C/N0 data for all 32 valid SDIDs + * + * Companion data + * companion_id (1-255. 10 is TeleScience) + * time of last companion data (seconds since boost) + * update_period (0.1-2.55 minimum telemetry interval) + * channels (0-12) + * channel data for all 12 possible channels + */ + + void write_general_header() { + out.printf("version,serial,flight,call,time,clock,rssi,lqi"); + } + + void write_general(AltosRecord record) { + out.printf("%s, %d, %d, %s, %8.2f, %8.2f, %4d, %3d", + ALTOS_CSV_VERSION, record.serial, record.flight, record.callsign, + (double) record.time, (double) record.tick / 100.0, + record.rssi, + record.status & 0x7f); + } + + void write_flight_header() { + out.printf("state,state_name"); + } + + void write_flight(AltosRecord record) { + out.printf("%d,%8s", record.state, record.state()); + } + + void write_basic_header() { + out.printf("acceleration,pressure,altitude,height,accel_speed,baro_speed,temperature,battery_voltage,drogue_voltage,main_voltage"); + } + + void write_basic(AltosRecord record) { + out.printf("%8.2f,%10.2f,%8.2f,%8.2f,%8.2f,%8.2f,%5.1f,%5.2f,%5.2f,%5.2f", + record.acceleration(), + record.raw_pressure(), + record.raw_altitude(), + record.raw_height(), + record.accel_speed(), + state.baro_speed, + record.temperature(), + record.battery_voltage(), + record.drogue_voltage(), + record.main_voltage()); + } + + void write_advanced_header() { + out.printf("accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z"); + } + + void write_advanced(AltosRecord record) { + AltosIMU imu = record.imu(); + AltosMag mag = record.mag(); + + if (imu == null) + imu = new AltosIMU(); + if (mag == null) + mag = new AltosMag(); + out.printf("%6d,%6d,%6d,%6d,%6d,%6d,%6d,%6d,%6d", + imu.accel_x, imu.accel_y, imu.accel_z, + imu.gyro_x, imu.gyro_y, imu.gyro_z, + mag.x, mag.y, mag.z); + } + + void write_gps_header() { + out.printf("connected,locked,nsat,latitude,longitude,altitude,year,month,day,hour,minute,second,pad_dist,pad_range,pad_az,pad_el,hdop"); + } + + void write_gps(AltosRecord record) { + AltosGPS gps = record.gps; + if (gps == null) + gps = new AltosGPS(); + + AltosGreatCircle from_pad = state.from_pad; + if (from_pad == null) + from_pad = new AltosGreatCircle(); + + out.printf("%2d,%2d,%3d,%12.7f,%12.7f,%6d,%5d,%3d,%3d,%3d,%3d,%3d,%9.0f,%9.0f,%4.0f,%4.0f,%6.1f", + gps.connected?1:0, + gps.locked?1:0, + gps.nsat, + gps.lat, + gps.lon, + gps.alt, + gps.year, + gps.month, + gps.day, + gps.hour, + gps.minute, + gps.second, + from_pad.distance, + state.range, + from_pad.bearing, + state.elevation, + gps.hdop); + } + + void write_gps_sat_header() { + for(int i = 1; i <= 32; i++) { + out.printf("sat%02d", i); + if (i != 32) + out.printf(","); + } + } + + void write_gps_sat(AltosRecord record) { + AltosGPS gps = record.gps; + for(int i = 1; i <= 32; i++) { + int c_n0 = 0; + if (gps != null && gps.cc_gps_sat != null) { + for(int j = 0; j < gps.cc_gps_sat.length; j++) + if (gps.cc_gps_sat[j].svid == i) { + c_n0 = gps.cc_gps_sat[j].c_n0; + break; + } + } + out.printf ("%3d", c_n0); + if (i != 32) + out.printf(","); + } + } + + void write_companion_header() { + out.printf("companion_id,companion_time,companion_update,companion_channels"); + for (int i = 0; i < 12; i++) + out.printf(",companion_%02d", i); + } + + void write_companion(AltosRecord record) { + AltosRecordCompanion companion = record.companion; + + int channels_written = 0; + if (companion == null) { + out.printf("0,0,0,0"); + } else { + out.printf("%3d,%5.2f,%5.2f,%2d", + companion.board_id, + (companion.tick - boost_tick) / 100.0, + companion.update_period / 100.0, + companion.channels); + for (; channels_written < companion.channels; channels_written++) + out.printf(",%5d", companion.companion_data[channels_written]); + } + for (; channels_written < 12; channels_written++) + out.printf(",0"); + } + + void write_header(boolean advanced, boolean gps, boolean companion) { + out.printf("#"); write_general_header(); + out.printf(","); write_flight_header(); + out.printf(","); write_basic_header(); + if (advanced) + out.printf(","); write_advanced_header(); + if (gps) { + out.printf(","); write_gps_header(); + out.printf(","); write_gps_sat_header(); + } + if (companion) { + out.printf(","); write_companion_header(); + } + out.printf ("\n"); + } + + void write_one(AltosRecord record) { + state = new AltosState(record, state); + write_general(record); out.printf(","); + write_flight(record); out.printf(","); + write_basic(record); out.printf(","); + if (record.imu() != null || record.mag() != null) + write_advanced(record); + if (record.gps != null) { + out.printf(","); + write_gps(record); out.printf(","); + write_gps_sat(record); + } + if (record.companion != null) { + out.printf(","); + write_companion(record); + } + out.printf ("\n"); + } + + void flush_pad() { + while (!pad_records.isEmpty()) { + write_one (pad_records.remove()); + } + } + + public void write(AltosRecord record) { + if (record.state == Altos.ao_flight_startup) + return; + if (!header_written) { + write_header(record.imu() != null || record.mag() != null, + record.gps != null, record.companion != null); + header_written = true; + } + if (!seen_boost) { + if (record.state >= Altos.ao_flight_boost) { + seen_boost = true; + boost_tick = record.tick; + flush_pad(); + } + } + if (seen_boost) + write_one(record); + else + pad_records.add(record); + } + + public PrintStream out() { + return out; + } + + public void close() { + if (!pad_records.isEmpty()) { + boost_tick = pad_records.element().tick; + flush_pad(); + } + out.close(); + } + + public void write(AltosRecordIterable iterable) { + iterable.write_comments(out()); + for (AltosRecord r : iterable) + write(r); + } + + public AltosCSV(PrintStream in_out, File in_name) { + name = in_name; + out = in_out; + pad_records = new LinkedList<AltosRecord>(); + } + + public AltosCSV(File in_name) throws FileNotFoundException { + this(new PrintStream(in_name), in_name); + } + + public AltosCSV(String in_string) throws FileNotFoundException { + this(new File(in_string)); + } +} diff --git a/altosui/AltosCSVUI.java b/altosui/AltosCSVUI.java new file mode 100644 index 00000000..2702668b --- /dev/null +++ b/altosui/AltosCSVUI.java @@ -0,0 +1,109 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosCSVUI + extends AltosDialog + implements ActionListener +{ + JFileChooser csv_chooser; + JPanel accessory; + JComboBox combo_box; + AltosRecordIterable iterable; + AltosWriter writer; + + static String[] combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" }; + + void set_default_file() { + File current = csv_chooser.getSelectedFile(); + String current_name = current.getName(); + String new_name = null; + String selected = (String) combo_box.getSelectedItem(); + + if (selected.contains("CSV")) + new_name = Altos.replace_extension(current_name, ".csv"); + else if (selected.contains("KML")) + new_name = Altos.replace_extension(current_name, ".kml"); + if (new_name != null) + csv_chooser.setSelectedFile(new File(new_name)); + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("comboBoxChanged")) + set_default_file(); + } + + public AltosCSVUI(JFrame frame, AltosRecordIterable in_iterable, File source_file) { + iterable = in_iterable; + csv_chooser = new JFileChooser(source_file); + + accessory = new JPanel(); + accessory.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.weightx = 1; + c.weighty = 0; + c.insets = new Insets (4, 4, 4, 4); + + JLabel accessory_label = new JLabel("Export File Type"); + c.gridx = 0; + c.gridy = 0; + accessory.add(accessory_label, c); + + combo_box = new JComboBox(combo_box_items); + combo_box.addActionListener(this); + c.gridx = 0; + c.gridy = 1; + accessory.add(combo_box, c); + + csv_chooser.setAccessory(accessory); + csv_chooser.setSelectedFile(source_file); + set_default_file(); + int ret = csv_chooser.showSaveDialog(frame); + if (ret == JFileChooser.APPROVE_OPTION) { + File file = csv_chooser.getSelectedFile(); + String type = (String) combo_box.getSelectedItem(); + try { + if (type.contains("CSV")) + writer = new AltosCSV(file); + else + writer = new AltosKML(file); + writer.write(iterable); + writer.close(); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(frame, + ee.getMessage(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/altosui/AltosChannelMenu.java b/altosui/AltosChannelMenu.java new file mode 100644 index 00000000..0249a0bd --- /dev/null +++ b/altosui/AltosChannelMenu.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosChannelMenu extends JComboBox implements ActionListener { + int channel; + + public AltosChannelMenu(int current_channel) { + + channel = current_channel; + + for (int c = 0; c <= 9; c++) + addItem(String.format("Channel %1d (%7.3fMHz)", c, 434.550 + c * 0.1)); + setSelectedIndex(channel); + setMaximumRowCount(10); + } + +} diff --git a/altosui/AltosCompanionInfo.java b/altosui/AltosCompanionInfo.java new file mode 100644 index 00000000..4ba8fe98 --- /dev/null +++ b/altosui/AltosCompanionInfo.java @@ -0,0 +1,113 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosCompanionInfo extends JTable { + private AltosFlightInfoTableModel model; + + static final int info_columns = 2; + static final int info_rows = 17; + + int desired_row_height() { + FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font); + return (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 18 / 10; + } + + public void set_font() { + setFont(Altos.table_value_font); + setRowHeight(desired_row_height()); + doLayout(); + } + + public AltosCompanionInfo() { + super(new AltosFlightInfoTableModel(info_rows, info_columns)); + model = (AltosFlightInfoTableModel) getModel(); + setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS); + setShowGrid(true); + set_font(); + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + void info_reset() { + model.reset(); + } + + void info_add_row(int col, String name, String value) { + model.addRow(col, name, value); + } + + void info_add_row(int col, String name, String format, Object... parameters) { + info_add_row (col, name, String.format(format, parameters)); + } + + void info_finish() { + model.finish(); + } + + public void clear() { + model.clear(); + } + + AltosRecordCompanion companion; + + public String board_name() { + if (companion == null) + return "None"; + switch (companion.board_id) { + case AltosRecordCompanion.board_id_telescience: + return "TeleScience"; + default: + return String.format("%02x\n", companion.board_id); + } + } + + public void show(AltosState state, int crc_errors) { + if (state == null) + return; + if (state.data.companion != null) + companion = state.data.companion; + info_reset(); + info_add_row(0, "Companion board", "%s", board_name()); + if (companion != null) { + info_add_row(0, "Last Data", "%5d", companion.tick); + info_add_row(0, "Update period", "%5.2f s", + companion.update_period / 100.0); + info_add_row(0, "Channels", "%3d", companion.channels); + + for (int i = 0; i < companion.channels; i++) + info_add_row(1, String.format("Channel %2d", i), + "%6d", companion.companion_data[i]); + } + info_finish(); + } +} diff --git a/altosui/AltosConfig.java b/altosui/AltosConfig.java new file mode 100644 index 00000000..cae41858 --- /dev/null +++ b/altosui/AltosConfig.java @@ -0,0 +1,521 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosConfig implements ActionListener { + + class int_ref { + int value; + + public int get() { + return value; + } + public void set(int i) { + value = i; + } + public int_ref(int i) { + value = i; + } + } + + class string_ref { + String value; + + public String get() { + return value; + } + public void set(String i) { + value = i; + } + public string_ref(String i) { + value = i; + } + } + + JFrame owner; + AltosDevice device; + AltosSerial serial_line; + boolean remote; + AltosConfigData remote_config_data; + double remote_frequency; + int_ref serial; + int_ref log_format; + int_ref main_deploy; + int_ref apogee_delay; + int_ref apogee_lockout; + int_ref radio_channel; + int_ref radio_calibration; + int_ref flight_log_max; + int_ref ignite_mode; + int_ref pad_orientation; + int_ref radio_setting; + int_ref radio_frequency; + int_ref storage_size; + int_ref storage_erase_unit; + int_ref stored_flight; + int_ref radio_enable; + string_ref version; + string_ref product; + string_ref callsign; + AltosConfigUI config_ui; + boolean serial_started; + boolean made_visible; + + boolean get_int(String line, String label, int_ref x) { + if (line.startsWith(label)) { + try { + String tail = line.substring(label.length()).trim(); + String[] tokens = tail.split("\\s+"); + if (tokens.length > 0) { + int i = Integer.parseInt(tokens[0]); + x.set(i); + return true; + } + } catch (NumberFormatException ne) { + } + } + return false; + } + + boolean get_string(String line, String label, string_ref s) { + if (line.startsWith(label)) { + String quoted = line.substring(label.length()).trim(); + + if (quoted.startsWith("\"")) + quoted = quoted.substring(1); + if (quoted.endsWith("\"")) + quoted = quoted.substring(0,quoted.length()-1); + s.set(quoted); + return true; + } else { + return false; + } + } + + void start_serial() throws InterruptedException, TimeoutException { + serial_started = true; + if (remote) + serial_line.start_remote(); + } + + void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (remote) + serial_line.stop_remote(); + } + + int log_limit() { + if (storage_size.get() > 0 && storage_erase_unit.get() > 0) { + int log_limit = storage_size.get() - storage_erase_unit.get(); + if (log_limit > 0) + return log_limit / 1024; + } + return 1024; + } + + void update_ui() { + config_ui.set_serial(serial.get()); + config_ui.set_product(product.get()); + config_ui.set_version(version.get()); + config_ui.set_main_deploy(main_deploy.get()); + config_ui.set_apogee_delay(apogee_delay.get()); + config_ui.set_apogee_lockout(apogee_lockout.get()); + config_ui.set_radio_calibration(radio_calibration.get()); + config_ui.set_radio_frequency(frequency()); + boolean max_enabled = true; + switch (log_format.get()) { + case Altos.AO_LOG_FORMAT_TINY: + max_enabled = false; + break; + default: + if (stored_flight.get() >= 0) + max_enabled = false; + break; + } + config_ui.set_flight_log_max_enabled(max_enabled); + config_ui.set_radio_enable(radio_enable.get()); + config_ui.set_flight_log_max_limit(log_limit()); + config_ui.set_flight_log_max(flight_log_max.get()); + config_ui.set_ignite_mode(ignite_mode.get()); + config_ui.set_pad_orientation(pad_orientation.get()); + config_ui.set_callsign(callsign.get()); + config_ui.set_clean(); + if (!made_visible) { + made_visible = true; + config_ui.make_visible(); + } + } + + void process_line(String line) { + if (line == null) { + abort(); + return; + } + if (line.equals("all finished")) { + if (serial_line != null) + update_ui(); + return; + } + get_int(line, "serial-number", serial); + get_int(line, "log-format", log_format); + get_int(line, "Main deploy:", main_deploy); + get_int(line, "Apogee delay:", apogee_delay); + get_int(line, "Apogee lockout:", apogee_lockout); + get_int(line, "Radio channel:", radio_channel); + get_int(line, "Radio cal:", radio_calibration); + get_int(line, "Max flight log:", flight_log_max); + get_int(line, "Ignite mode:", ignite_mode); + get_int(line, "Pad orientation:", pad_orientation); + get_int(line, "Radio setting:", radio_setting); + if (get_int(line, "Frequency:", radio_frequency)) + if (radio_frequency.get() < 0) + radio_frequency.set(434550); + get_int(line, "Radio enable:", radio_enable); + get_int(line, "Storage size:", storage_size); + get_int(line, "Storage erase unit:", storage_erase_unit); + get_int(line, "flight", stored_flight); + get_string(line, "Callsign:", callsign); + get_string(line,"software-version", version); + get_string(line,"product", product); + } + + final static int serial_mode_read = 0; + final static int serial_mode_save = 1; + final static int serial_mode_reboot = 2; + + class SerialData implements Runnable { + AltosConfig config; + int serial_mode; + + void process_line(String line) { + config.process_line(line); + } + void callback(String in_line) { + final String line = in_line; + Runnable r = new Runnable() { + public void run() { + process_line(line); + } + }; + SwingUtilities.invokeLater(r); + } + + void reset_data() { + serial.set(0); + log_format.set(Altos.AO_LOG_FORMAT_UNKNOWN); + main_deploy.set(250); + apogee_delay.set(0); + apogee_lockout.set(0); + radio_channel.set(0); + radio_setting.set(0); + radio_frequency.set(0); + radio_calibration.set(1186611); + radio_enable.set(-1); + flight_log_max.set(0); + ignite_mode.set(-1); + pad_orientation.set(-1); + storage_size.set(-1); + storage_erase_unit.set(-1); + stored_flight.set(-1); + callsign.set("N0CALL"); + version.set("unknown"); + product.set("unknown"); + } + + void get_data() { + try { + config.start_serial(); + reset_data(); + + config.serial_line.printf("c s\nf\nl\nv\n"); + for (;;) { + try { + String line = config.serial_line.get_reply(5000); + if (line == null) + stop_serial(); + callback(line); + if (line.startsWith("software-version")) + break; + } catch (Exception e) { + break; + } + } + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + callback("all finished"); + } + + void save_data() { + try { + double frequency = frequency(); + boolean has_frequency = radio_frequency.get() > 0; + boolean has_setting = radio_setting.get() > 0; + start_serial(); + serial_line.printf("c m %d\n", main_deploy.get()); + serial_line.printf("c d %d\n", apogee_delay.get()); + serial_line.printf("c L %d\n", apogee_lockout.get()); + if (!remote) + serial_line.printf("c f %d\n", radio_calibration.get()); + serial_line.set_radio_frequency(frequency, + has_frequency, + has_setting, + radio_calibration.get()); + if (remote) { + serial_line.stop_remote(); + serial_line.set_radio_frequency(frequency); + AltosUIPreferences.set_frequency(device.getSerial(), frequency); + serial_line.start_remote(); + } + serial_line.printf("c c %s\n", callsign.get()); + if (flight_log_max.get() != 0) + serial_line.printf("c l %d\n", flight_log_max.get()); + if (radio_enable.get() >= 0) + serial_line.printf("c e %d\n", radio_enable.get()); + if (ignite_mode.get() >= 0) + serial_line.printf("c i %d\n", ignite_mode.get()); + if (pad_orientation.get() >= 0) + serial_line.printf("c o %d\n", pad_orientation.get()); + serial_line.printf("c w\n"); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + void reboot() { + try { + start_serial(); + serial_line.printf("r eboot\n"); + serial_line.flush_output(); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + serial_line.close(); + } + } + + public void run () { + switch (serial_mode) { + case serial_mode_save: + save_data(); + /* fall through ... */ + case serial_mode_read: + get_data(); + break; + case serial_mode_reboot: + reboot(); + break; + } + } + + public SerialData(AltosConfig in_config, int in_serial_mode) { + config = in_config; + serial_mode = in_serial_mode; + } + } + + void run_serial_thread(int serial_mode) { + SerialData sd = new SerialData(this, serial_mode); + Thread st = new Thread(sd); + st.start(); + } + + void init_ui () throws InterruptedException, TimeoutException { + config_ui = new AltosConfigUI(owner, remote); + config_ui.addActionListener(this); + serial_line.set_frame(owner); + set_ui(); + } + + void abort() { + serial_line.close(); + serial_line = null; + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + config_ui.setVisible(false); + } + + void set_ui() throws InterruptedException, TimeoutException { + if (serial_line != null) + run_serial_thread(serial_mode_read); + else + update_ui(); + } + + double frequency() { + return AltosConvert.radio_to_frequency(radio_frequency.get(), + radio_setting.get(), + radio_calibration.get(), + radio_channel.get()); + } + + void set_frequency(double freq) { + int frequency = radio_frequency.get(); + int setting = radio_setting.get(); + + if (frequency > 0) { + radio_frequency.set((int) Math.floor (freq * 1000 + 0.5)); + radio_channel.set(0); + } else if (setting > 0) { + radio_setting.set(AltosConvert.radio_frequency_to_setting(freq, + radio_calibration.get())); + radio_channel.set(0); + } else { + radio_channel.set(AltosConvert.radio_frequency_to_channel(freq)); + } + } + + void save_data() { + + /* bounds check stuff */ + if (config_ui.flight_log_max() > log_limit()) { + JOptionPane.showMessageDialog(owner, + String.format("Requested flight log, %dk, is larger than the available space, %dk.\n", + config_ui.flight_log_max(), + log_limit()), + "Maximum Flight Log Too Large", + JOptionPane.ERROR_MESSAGE); + return; + } + + main_deploy.set(config_ui.main_deploy()); + apogee_delay.set(config_ui.apogee_delay()); + apogee_lockout.set(config_ui.apogee_lockout()); + radio_calibration.set(config_ui.radio_calibration()); + set_frequency(config_ui.radio_frequency()); + flight_log_max.set(config_ui.flight_log_max()); + if (radio_enable.get() >= 0) + radio_enable.set(config_ui.radio_enable()); + if (ignite_mode.get() >= 0) + ignite_mode.set(config_ui.ignite_mode()); + if (pad_orientation.get() >= 0) + pad_orientation.set(config_ui.pad_orientation()); + callsign.set(config_ui.callsign()); + run_serial_thread(serial_mode_save); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + try { + if (cmd.equals("Save")) { + save_data(); + } else if (cmd.equals("Reset")) { + set_ui(); + } else if (cmd.equals("Reboot")) { + if (serial_line != null) + run_serial_thread(serial_mode_reboot); + } else if (cmd.equals("Close")) { + if (serial_line != null) + serial_line.close(); + } + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public AltosConfig(JFrame given_owner) { + owner = given_owner; + + serial = new int_ref(0); + log_format = new int_ref(Altos.AO_LOG_FORMAT_UNKNOWN); + main_deploy = new int_ref(250); + apogee_delay = new int_ref(0); + apogee_lockout = new int_ref(0); + radio_channel = new int_ref(0); + radio_setting = new int_ref(0); + radio_frequency = new int_ref(0); + radio_calibration = new int_ref(1186611); + radio_enable = new int_ref(-1); + flight_log_max = new int_ref(0); + ignite_mode = new int_ref(-1); + pad_orientation = new int_ref(-1); + storage_size = new int_ref(-1); + storage_erase_unit = new int_ref(-1); + stored_flight = new int_ref(-1); + callsign = new string_ref("N0CALL"); + version = new string_ref("unknown"); + product = new string_ref("unknown"); + + device = AltosDeviceDialog.show(owner, Altos.product_any); + if (device != null) { + try { + serial_line = new AltosSerial(device); + try { + if (!device.matchProduct(Altos.product_altimeter)) + remote = true; + init_ui(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + } +}
\ No newline at end of file diff --git a/altosui/AltosConfigFreqUI.java b/altosui/AltosConfigFreqUI.java new file mode 100644 index 00000000..7958a21c --- /dev/null +++ b/altosui/AltosConfigFreqUI.java @@ -0,0 +1,419 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import javax.swing.plaf.basic.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +class AltosEditFreqUI extends AltosDialog implements ActionListener { + Frame frame; + JTextField frequency; + JTextField description; + JButton ok_button, cancel_button; + boolean got_ok; + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if ("ok".equals(cmd)) { + got_ok = true; + setVisible(false); + } + if ("cancel".equals(cmd)) { + got_ok = false; + setVisible(false); + } + } + + public AltosFrequency get() { + if (!got_ok) + return null; + + String f_s = frequency.getText(); + String d_s = description.getText(); + + try { + double f_d = Double.parseDouble(f_s); + + return new AltosFrequency(f_d, d_s); + } catch (NumberFormatException ne) { + } + return null; + } + + public AltosEditFreqUI(Frame in_frame, AltosFrequency existing) { + super(in_frame, true); + + got_ok = false; + frame = in_frame; + + Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets (4,4,4,4); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(new JLabel("Frequency"), c); + + frequency = new JTextField(12); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(frequency, c); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(new JLabel("Description"), c); + + description = new JTextField(12); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(description, c); + + ok_button = new JButton("OK"); + ok_button.setActionCommand("ok"); + ok_button.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(ok_button, c); + + cancel_button = new JButton("Cancel"); + cancel_button.setActionCommand("cancel"); + cancel_button.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(cancel_button, c); + + if (existing == null) + setTitle("Add New Frequency"); + else { + setTitle("Edit Existing Frequency"); + frequency.setText(String.format("%7.3f", existing.frequency)); + description.setText(existing.description); + } + getRootPane().setDefaultButton(ok_button); + + pack(); + setLocationRelativeTo(frame); + + } + + public AltosEditFreqUI(Frame in_frame) { + this(in_frame, (AltosFrequency) null); + } +} + +public class AltosConfigFreqUI extends AltosDialog implements ActionListener { + + Frame frame; + LinkedList<ActionListener> listeners; + + class FrequencyList extends JList { + DefaultListModel list_model; + + public void add(AltosFrequency frequency) { + int i; + for (i = 0; i < list_model.size(); i++) { + AltosFrequency f = (AltosFrequency) list_model.get(i); + if (frequency.frequency == f.frequency) + return; + if (frequency.frequency < f.frequency) + break; + } + list_model.insertElementAt(frequency, i); + } + + public void remove(AltosFrequency frequency) { + list_model.removeElement(frequency); + } + + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + + public AltosFrequency selected() { + AltosFrequency f = (AltosFrequency) getSelectedValue(); + return f; + } + + public AltosFrequency[] frequencies() { + AltosFrequency[] ret; + + ret = new AltosFrequency[list_model.size()]; + for (int i = 0; i < list_model.size(); i++) + ret[i] = (AltosFrequency) list_model.get(i); + return ret; + } + + public FrequencyList(AltosFrequency[] in_frequencies) { + list_model = new DefaultListModel(); + setModel(list_model); + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + setLayoutOrientation(JList.HORIZONTAL_WRAP); + for (int i = 0; i < in_frequencies.length; i++) { + add(in_frequencies[i]); + } + setVisibleRowCount(in_frequencies.length); + } + } + + FrequencyList frequencies; + + void save_frequencies() { + AltosUIPreferences.set_common_frequencies(frequencies.frequencies()); + } + + JButton add, edit, remove; + + JButton cancel, ok; + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if ("ok".equals(cmd)) { + save_frequencies(); + setVisible(false); + } else if ("cancel".equals(cmd)) { + setVisible(false); + } else if ("add".equals(cmd)) { + AltosEditFreqUI ui = new AltosEditFreqUI(frame); + ui.setVisible(true); + AltosFrequency f = ui.get(); + if (f != null) + frequencies.add(f); + } else if ("edit".equals(cmd)) { + AltosFrequency old_f = frequencies.selected(); + if (old_f == null) + return; + AltosEditFreqUI ui = new AltosEditFreqUI(frame, old_f); + ui.setVisible(true); + AltosFrequency new_f = ui.get(); + if (new_f != null) { + if (old_f != null) + frequencies.remove(old_f); + frequencies.add(new_f); + } + } else if ("remove".equals(cmd)) { + AltosFrequency old_f = frequencies.selected(); + if (old_f == null) + return; + int ret = JOptionPane.showConfirmDialog(this, + String.format("Remove frequency \"%s\"?", + old_f.toShortString()), + "Remove Frequency", + JOptionPane.YES_NO_OPTION); + if (ret == JOptionPane.YES_OPTION) + frequencies.remove(old_f); + } + } + + public AltosFrequency[] frequencies() { + return frequencies.frequencies(); + } + + public AltosConfigFreqUI(Frame in_frame, + AltosFrequency[] in_frequencies) { + super(in_frame, "Manage Frequencies", true); + + frame = in_frame; + + listeners = new LinkedList<ActionListener>(); + + frequencies = new FrequencyList(in_frequencies); + + Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(4,4,4,4); + + /* + * Frequencies label and list + */ + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(new JLabel("Frequencies"), c); + + JScrollPane list_scroller = new JScrollPane(frequencies); + list_scroller.setAlignmentX(LEFT_ALIGNMENT); + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 6; + c.gridheight = 2; + c.weightx = 1; + c.weighty = 1; + pane.add(list_scroller, c); + + add = new JButton("Add"); + add.setActionCommand("add"); + add.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 2; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(add, c); + + edit = new JButton("Edit"); + edit.setActionCommand("edit"); + edit.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 2; + c.gridy = 3; + c.gridwidth = 2; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(edit, c); + + remove = new JButton("Remove"); + remove.setActionCommand("remove"); + remove.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 4; + c.gridy = 3; + c.gridwidth = 2; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(remove, c); + + ok = new JButton("OK"); + ok.setActionCommand("ok"); + ok.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 3; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(ok, c); + + cancel = new JButton("Cancel"); + cancel.setActionCommand("cancel"); + cancel.addActionListener(this); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 3; + c.gridy = 4; + c.gridwidth = 3; + c.gridheight = 1; + c.weightx = 0; + c.weighty = 0; + pane.add(cancel, c); + + pack(); + setLocationRelativeTo(frame); + } + + public static void show(Component frameComp) { + Frame frame = JOptionPane.getFrameForComponent(frameComp); + AltosConfigFreqUI dialog; + + dialog = new AltosConfigFreqUI(frame, AltosUIPreferences.common_frequencies()); + dialog.setVisible(true); + } + +} diff --git a/altosui/AltosConfigTD.java b/altosui/AltosConfigTD.java new file mode 100644 index 00000000..324a5988 --- /dev/null +++ b/altosui/AltosConfigTD.java @@ -0,0 +1,374 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +import libaltosJNI.*; + +import org.altusmetrum.AltosLib.*; + +public class AltosConfigTD implements ActionListener { + + class int_ref { + int value; + + public int get() { + return value; + } + public void set(int i) { + value = i; + } + public int_ref(int i) { + value = i; + } + } + + class string_ref { + String value; + + public String get() { + return value; + } + public void set(String i) { + value = i; + } + public string_ref(String i) { + value = i; + } + } + + JFrame owner; + AltosDevice device; + AltosSerial serial_line; + int_ref serial; + int_ref radio_channel; + int_ref radio_calibration; + int_ref radio_setting; + int_ref radio_frequency; + string_ref config_version; + string_ref version; + string_ref product; + AltosConfigTDUI config_ui; + boolean serial_started; + boolean made_visible; + + boolean get_int(String line, String label, int_ref x) { + if (line.startsWith(label)) { + try { + String tail = line.substring(label.length()).trim(); + String[] tokens = tail.split("\\s+"); + if (tokens.length > 0) { + int i = Integer.parseInt(tokens[0]); + x.set(i); + return true; + } + } catch (NumberFormatException ne) { + } + } + return false; + } + + boolean get_string(String line, String label, string_ref s) { + if (line.startsWith(label)) { + String quoted = line.substring(label.length()).trim(); + + if (quoted.startsWith("\"")) + quoted = quoted.substring(1); + if (quoted.endsWith("\"")) + quoted = quoted.substring(0,quoted.length()-1); + s.set(quoted); + return true; + } else { + return false; + } + } + + void start_serial() throws InterruptedException, TimeoutException { + serial_started = true; + } + + void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + } + + void update_ui() { + config_ui.set_serial(serial.get()); + config_ui.set_product(product.get()); + config_ui.set_version(version.get()); + config_ui.set_radio_frequency(frequency()); + config_ui.set_radio_calibration(radio_calibration.get()); + config_ui.set_clean(); + if (!made_visible) { + made_visible = true; + config_ui.make_visible(); + } + } + + void process_line(String line) { + if (line == null) { + abort(); + return; + } + if (line.equals("all finished")) { + if (serial_line != null) + update_ui(); + return; + } + get_string(line, "Config version", config_version); + get_int(line, "serial-number", serial); + get_int(line, "Radio channel:", radio_channel); + get_int(line, "Radio cal:", radio_calibration); + get_int(line, "Frequency:", radio_frequency); + get_int(line, "Radio setting:", radio_setting); + get_string(line,"software-version", version); + get_string(line,"product", product); + } + + final static int serial_mode_read = 0; + final static int serial_mode_save = 1; + final static int serial_mode_reboot = 2; + + class SerialData implements Runnable { + AltosConfigTD config; + int serial_mode; + + void process_line(String line) { + config.process_line(line); + } + void callback(String in_line) { + final String line = in_line; + Runnable r = new Runnable() { + public void run() { + process_line(line); + } + }; + SwingUtilities.invokeLater(r); + } + + void reset_data() { + serial.set(0); + radio_channel.set(0); + radio_setting.set(0); + radio_frequency.set(0); + radio_calibration.set(1186611); + config_version.set("0.0"); + version.set("unknown"); + product.set("unknown"); + } + + void get_data() { + try { + boolean been_there = false; + config.start_serial(); + reset_data(); + + for (;;) { + config.serial_line.printf("c s\nf\nl\nv\n"); + for (;;) { + try { + String line = config.serial_line.get_reply(5000); + if (line == null) + stop_serial(); + callback(line); + if (line.startsWith("software-version")) + break; + } catch (Exception e) { + break; + } + } + if (been_there) + break; + if (!config_version.get().equals("0.0")) + break; + been_there = true; + config.serial_line.printf("C\n "); + config.serial_line.flush_input(); + } + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + double pref_frequency = AltosPreferences.frequency(serial.get()); + if (pref_frequency != 0) + radio_frequency.set((int) Math.floor (pref_frequency * 1000 + 0.5)); + callback("all finished"); + } + + void save_data() { + double frequency = frequency(); + if (frequency != 0) + AltosPreferences.set_frequency(serial.get(), + frequency); + } + + public void run () { + switch (serial_mode) { + case serial_mode_save: + save_data(); + /* fall through ... */ + case serial_mode_read: + get_data(); + break; + } + } + + public SerialData(AltosConfigTD in_config, int in_serial_mode) { + config = in_config; + serial_mode = in_serial_mode; + } + } + + void run_serial_thread(int serial_mode) { + SerialData sd = new SerialData(this, serial_mode); + Thread st = new Thread(sd); + st.start(); + } + + void init_ui () throws InterruptedException, TimeoutException { + config_ui = new AltosConfigTDUI(owner); + config_ui.addActionListener(this); + serial_line.set_frame(owner); + set_ui(); + } + + void abort() { + serial_line.close(); + serial_line = null; + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + config_ui.setVisible(false); + } + + void set_ui() throws InterruptedException, TimeoutException { + if (serial_line != null) + run_serial_thread(serial_mode_read); + else + update_ui(); + } + + double frequency() { + return AltosConvert.radio_to_frequency(radio_frequency.get(), + radio_setting.get(), + radio_calibration.get(), + radio_channel.get()); + } + + void set_frequency(double freq) { + int frequency = radio_frequency.get(); + int setting = radio_setting.get(); + + if (frequency > 0) { + radio_frequency.set((int) Math.floor (freq * 1000 + 0.5)); + } else if (setting > 0) { + radio_setting.set(AltosConvert.radio_frequency_to_setting(freq, + radio_calibration.get())); + radio_channel.set(0); + } else { + radio_channel.set(AltosConvert.radio_frequency_to_channel(freq)); + } + } + + void save_data() { + + set_frequency(config_ui.radio_frequency()); + run_serial_thread(serial_mode_save); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + try { + if (cmd.equals("Save")) { + save_data(); + } else if (cmd.equals("Reset")) { + set_ui(); + } else if (cmd.equals("Reboot")) { + if (serial_line != null) + run_serial_thread(serial_mode_reboot); + } else if (cmd.equals("Close")) { + if (serial_line != null) + serial_line.close(); + } + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public AltosConfigTD(JFrame given_owner) { + owner = given_owner; + + serial = new int_ref(0); + radio_channel = new int_ref(0); + radio_setting = new int_ref(0); + radio_frequency = new int_ref(0); + radio_calibration = new int_ref(1186611); + config_version = new string_ref("0.0"); + version = new string_ref("unknown"); + product = new string_ref("unknown"); + + device = AltosDeviceDialog.show(owner, Altos.product_basestation); + if (device != null) { + try { + serial_line = new AltosSerial(device); + try { + init_ui(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + } +}
\ No newline at end of file diff --git a/altosui/AltosConfigTDUI.java b/altosui/AltosConfigTDUI.java new file mode 100644 index 00000000..f2058f69 --- /dev/null +++ b/altosui/AltosConfigTDUI.java @@ -0,0 +1,355 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +import libaltosJNI.*; + +import org.altusmetrum.AltosLib.*; + +public class AltosConfigTDUI + extends AltosDialog + implements ActionListener, ItemListener, DocumentListener +{ + + Container pane; + Box box; + JLabel product_label; + JLabel version_label; + JLabel serial_label; + JLabel frequency_label; + JLabel radio_calibration_label; + JLabel radio_frequency_label; + + public boolean dirty; + + JFrame owner; + JLabel product_value; + JLabel version_value; + JLabel serial_value; + AltosFreqList radio_frequency_value; + JLabel radio_calibration_value; + + JButton save; + JButton reset; + JButton reboot; + JButton close; + + ActionListener listener; + + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigTDUI ui; + + public ConfigListener(AltosConfigTDUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "Close")); + } + } + + /* Build the UI using a grid bag */ + public AltosConfigTDUI(JFrame in_owner) { + super (in_owner, "Configure TeleDongle", false); + + owner = in_owner; + GridBagConstraints c; + + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + /* Product */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + product_label = new JLabel("Product:"); + pane.add(product_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 0; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + product_value = new JLabel(""); + pane.add(product_value, c); + + /* Version */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 1; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + version_label = new JLabel("Software version:"); + pane.add(version_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 1; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + version_value = new JLabel(""); + pane.add(version_value, c); + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + /* Frequency */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_frequency_label = new JLabel("Frequency:"); + pane.add(radio_frequency_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_frequency_value = new AltosFreqList(); + radio_frequency_value.addItemListener(this); + pane.add(radio_frequency_value, c); + radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency"); + + /* Radio Calibration */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("RF Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JLabel(String.format("%d", 1186611)); + pane.add(radio_calibration_value, c); + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 12; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + save = new JButton("Save"); + pane.add(save, c); + save.addActionListener(this); + save.setActionCommand("Save"); + + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = 12; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reset = new JButton("Reset"); + pane.add(reset, c); + reset.addActionListener(this); + reset.setActionCommand("Reset"); + + c = new GridBagConstraints(); + c.gridx = 6; c.gridy = 12; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_END; + c.insets = il; + close = new JButton("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("Close"); + + addWindowListener(new ConfigListener(this)); + } + + /* Once the initial values are set, the config code will show the dialog */ + public void make_visible() { + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + /* If any values have been changed, confirm before closing */ + public boolean check_dirty(String operation) { + if (dirty) { + Object[] options = { String.format("%s anyway", operation), "Keep editing" }; + int i; + i = JOptionPane.showOptionDialog(this, + String.format("Configuration modified. %s anyway?", operation), + "Configuration Modified", + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, options, options[1]); + if (i != 0) + return false; + } + return true; + } + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("Close") || cmd.equals("Reboot")) + if (!check_dirty(cmd)) + return; + listener.actionPerformed(e); + if (cmd.equals("Close") || cmd.equals("Reboot")) { + setVisible(false); + dispose(); + } + dirty = false; + } + + /* ItemListener interface method */ + public void itemStateChanged(ItemEvent e) { + dirty = true; + } + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + dirty = true; + } + + public void insertUpdate(DocumentEvent e) { + dirty = true; + } + + public void removeUpdate(DocumentEvent e) { + dirty = true; + } + + /* Let the config code hook on a listener */ + public void addActionListener(ActionListener l) { + listener = l; + } + + /* set and get all of the dialog values */ + public void set_product(String product) { + radio_frequency_value.set_product(product); + product_value.setText(product); + } + + public void set_version(String version) { + version_value.setText(version); + } + + public void set_serial(int serial) { + radio_frequency_value.set_serial(serial); + serial_value.setText(String.format("%d", serial)); + } + + public void set_radio_frequency(double new_radio_frequency) { + int i; + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (f.close(new_radio_frequency)) { + radio_frequency_value.setSelectedIndex(i); + return; + } + } + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (new_radio_frequency < f.frequency) + break; + } + String description = String.format("%s serial %s", + product_value.getText(), + serial_value.getText()); + AltosFrequency new_frequency = new AltosFrequency(new_radio_frequency, description); + AltosPreferences.add_common_frequency(new_frequency); + radio_frequency_value.insertItemAt(new_frequency, i); + radio_frequency_value.setSelectedIndex(i); + } + + public double radio_frequency() { + return radio_frequency_value.frequency(); + } + + public void set_radio_calibration(int calibration) { + radio_calibration_value.setText(String.format("%d", calibration)); + } + + public void set_clean() { + dirty = false; + } +} diff --git a/altosui/AltosConfigUI.java b/altosui/AltosConfigUI.java new file mode 100644 index 00000000..62394fa6 --- /dev/null +++ b/altosui/AltosConfigUI.java @@ -0,0 +1,808 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosConfigUI + extends AltosDialog + implements ActionListener, ItemListener, DocumentListener +{ + + Container pane; + Box box; + JLabel product_label; + JLabel version_label; + JLabel serial_label; + JLabel main_deploy_label; + JLabel apogee_delay_label; + JLabel apogee_lockout_label; + JLabel frequency_label; + JLabel radio_calibration_label; + JLabel radio_frequency_label; + JLabel radio_enable_label; + JLabel flight_log_max_label; + JLabel ignite_mode_label; + JLabel pad_orientation_label; + JLabel callsign_label; + + public boolean dirty; + + JFrame owner; + JLabel product_value; + JLabel version_value; + JLabel serial_value; + JComboBox main_deploy_value; + JComboBox apogee_delay_value; + JComboBox apogee_lockout_value; + AltosFreqList radio_frequency_value; + JTextField radio_calibration_value; + JRadioButton radio_enable_value; + JComboBox flight_log_max_value; + JComboBox ignite_mode_value; + JComboBox pad_orientation_value; + JTextField callsign_value; + + JButton save; + JButton reset; + JButton reboot; + JButton close; + + ActionListener listener; + + static String[] main_deploy_values = { + "100", "150", "200", "250", "300", "350", + "400", "450", "500" + }; + + static String[] apogee_delay_values = { + "0", "1", "2", "3", "4", "5" + }; + + static String[] apogee_lockout_values = { + "0", "5", "10", "15", "20" + }; + + static String[] flight_log_max_values = { + "64", "128", "192", "256", "320", + "384", "448", "512", "576", "640", + "704", "768", "832", "896", "960", + }; + + static String[] ignite_mode_values = { + "Dual Deploy", + "Redundant Apogee", + "Redundant Main", + }; + + static String[] pad_orientation_values = { + "Antenna Up", + "Antenna Down", + }; + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigUI ui; + + public ConfigListener(AltosConfigUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "Close")); + } + } + + boolean is_telemini() { + String product = product_value.getText(); + return product != null && product.startsWith("TeleMini"); + } + + boolean is_telemetrum() { + String product = product_value.getText(); + return product != null && product.startsWith("TeleMetrum"); + } + + void set_radio_calibration_tool_tip() { + if (radio_calibration_value.isEnabled()) + radio_calibration_value.setToolTipText("Tune radio output to match desired frequency"); + else + radio_calibration_value.setToolTipText("Cannot tune radio while connected over packet mode"); + } + + void set_radio_enable_tool_tip() { + if (radio_enable_value.isEnabled()) + radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions"); + else + radio_enable_value.setToolTipText("Firmware version does not support disabling radio"); + } + + void set_flight_log_max_tool_tip() { + if (flight_log_max_value.isEnabled()) + flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)"); + else { + if (is_telemetrum()) + flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory"); + else if (is_telemini()) + flight_log_max_value.setToolTipText("TeleMini stores only one flight"); + else + flight_log_max_value.setToolTipText("Cannot set max flight log value"); + } + } + + void set_ignite_mode_tool_tip() { + if (ignite_mode_value.isEnabled()) + ignite_mode_value.setToolTipText("Select when igniters will be fired"); + else + ignite_mode_value.setToolTipText("Older firmware could not select ignite mode"); + } + + void set_pad_orientation_tool_tip() { + if (pad_orientation_value.isEnabled()) + pad_orientation_value.setToolTipText("How will TeleMetrum be mounted in the airframe"); + else { + if (is_telemetrum()) + pad_orientation_value.setToolTipText("Older TeleMetrum firmware must fly antenna forward"); + else if (is_telemini()) + pad_orientation_value.setToolTipText("TeleMini doesn't care how it is mounted"); + else + pad_orientation_value.setToolTipText("Can't select orientation"); + } + } + + /* Build the UI using a grid bag */ + public AltosConfigUI(JFrame in_owner, boolean remote) { + super (in_owner, "Configure TeleMetrum", false); + + owner = in_owner; + GridBagConstraints c; + int row = 0; + + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + /* Product */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + product_label = new JLabel("Product:"); + pane.add(product_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + product_value = new JLabel(""); + pane.add(product_value, c); + row++; + + /* Version */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + version_label = new JLabel("Software version:"); + pane.add(version_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + version_value = new JLabel(""); + pane.add(version_value, c); + row++; + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + serial_value = new JLabel(""); + pane.add(serial_value, c); + row++; + + /* Main deploy */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + main_deploy_label = new JLabel("Main Deploy Altitude(m):"); + pane.add(main_deploy_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + main_deploy_value = new JComboBox(main_deploy_values); + main_deploy_value.setEditable(true); + main_deploy_value.addItemListener(this); + pane.add(main_deploy_value, c); + main_deploy_value.setToolTipText("Height above pad altitude to fire main charge"); + row++; + + /* Apogee delay */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + apogee_delay_label = new JLabel("Apogee Delay(s):"); + pane.add(apogee_delay_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + apogee_delay_value = new JComboBox(apogee_delay_values); + apogee_delay_value.setEditable(true); + apogee_delay_value.addItemListener(this); + pane.add(apogee_delay_value, c); + apogee_delay_value.setToolTipText("Delay after apogee before charge fires"); + row++; + + /* Apogee lockout */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + apogee_lockout_label = new JLabel("Apogee Lockout(s):"); + pane.add(apogee_lockout_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + apogee_lockout_value = new JComboBox(apogee_lockout_values); + apogee_lockout_value.setEditable(true); + apogee_lockout_value.addItemListener(this); + pane.add(apogee_lockout_value, c); + apogee_lockout_value.setToolTipText("Time after boost while apogee detection is locked out"); + row++; + + /* Frequency */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_frequency_label = new JLabel("Frequency:"); + pane.add(radio_frequency_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_frequency_value = new AltosFreqList(); + radio_frequency_value.addItemListener(this); + pane.add(radio_frequency_value, c); + radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency"); + row++; + + /* Radio Calibration */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("RF Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JTextField(String.format("%d", 1186611)); + radio_calibration_value.getDocument().addDocumentListener(this); + if (remote) + radio_calibration_value.setEnabled(false); + pane.add(radio_calibration_value, c); + set_radio_calibration_tool_tip(); + row++; + + /* Radio Enable */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_enable_label = new JLabel("Telemetry/RDF Enable:"); + pane.add(radio_enable_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_enable_value = new JRadioButton("Enabled"); + radio_enable_value.addItemListener(this); + pane.add(radio_enable_value, c); + set_radio_enable_tool_tip(); + row++; + + /* Callsign */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + callsign_label = new JLabel("Callsign:"); + pane.add(callsign_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + callsign_value = new JTextField(AltosUIPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + pane.add(callsign_value, c); + callsign_value.setToolTipText("Callsign reported in telemetry data"); + row++; + + /* Flight log max */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + flight_log_max_label = new JLabel("Maximum Flight Log Size:"); + pane.add(flight_log_max_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + flight_log_max_value = new JComboBox(flight_log_max_values); + flight_log_max_value.setEditable(true); + flight_log_max_value.addItemListener(this); + pane.add(flight_log_max_value, c); + set_flight_log_max_tool_tip(); + row++; + + /* Ignite mode */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + ignite_mode_label = new JLabel("Igniter Firing Mode:"); + pane.add(ignite_mode_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + ignite_mode_value = new JComboBox(ignite_mode_values); + ignite_mode_value.setEditable(false); + ignite_mode_value.addItemListener(this); + pane.add(ignite_mode_value, c); + set_ignite_mode_tool_tip(); + row++; + + /* Pad orientation */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + pad_orientation_label = new JLabel("Pad Orientation:"); + pane.add(pad_orientation_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + pad_orientation_value = new JComboBox(pad_orientation_values); + pad_orientation_value.setEditable(false); + pad_orientation_value.addItemListener(this); + pane.add(pad_orientation_value, c); + set_pad_orientation_tool_tip(); + row++; + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + save = new JButton("Save"); + pane.add(save, c); + save.addActionListener(this); + save.setActionCommand("Save"); + + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reset = new JButton("Reset"); + pane.add(reset, c); + reset.addActionListener(this); + reset.setActionCommand("Reset"); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reboot = new JButton("Reboot"); + pane.add(reboot, c); + reboot.addActionListener(this); + reboot.setActionCommand("Reboot"); + + c = new GridBagConstraints(); + c.gridx = 6; c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_END; + c.insets = il; + close = new JButton("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("Close"); + + addWindowListener(new ConfigListener(this)); + } + + /* Once the initial values are set, the config code will show the dialog */ + public void make_visible() { + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + /* If any values have been changed, confirm before closing */ + public boolean check_dirty(String operation) { + if (dirty) { + Object[] options = { String.format("%s anyway", operation), "Keep editing" }; + int i; + i = JOptionPane.showOptionDialog(this, + String.format("Configuration modified. %s anyway?", operation), + "Configuration Modified", + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, options, options[1]); + if (i != 0) + return false; + } + return true; + } + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("Close") || cmd.equals("Reboot")) + if (!check_dirty(cmd)) + return; + listener.actionPerformed(e); + if (cmd.equals("Close") || cmd.equals("Reboot")) { + setVisible(false); + dispose(); + } + dirty = false; + } + + /* ItemListener interface method */ + public void itemStateChanged(ItemEvent e) { + dirty = true; + } + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + dirty = true; + } + + public void insertUpdate(DocumentEvent e) { + dirty = true; + } + + public void removeUpdate(DocumentEvent e) { + dirty = true; + } + + /* Let the config code hook on a listener */ + public void addActionListener(ActionListener l) { + listener = l; + } + + /* set and get all of the dialog values */ + public void set_product(String product) { + radio_frequency_value.set_product(product); + product_value.setText(product); + set_pad_orientation_tool_tip(); + set_flight_log_max_tool_tip(); + } + + public void set_version(String version) { + version_value.setText(version); + } + + public void set_serial(int serial) { + radio_frequency_value.set_serial(serial); + serial_value.setText(String.format("%d", serial)); + } + + public void set_main_deploy(int new_main_deploy) { + main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy)); + } + + public int main_deploy() { + return Integer.parseInt(main_deploy_value.getSelectedItem().toString()); + } + + public void set_apogee_delay(int new_apogee_delay) { + apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay)); + } + + public int apogee_delay() { + return Integer.parseInt(apogee_delay_value.getSelectedItem().toString()); + } + + public void set_apogee_lockout(int new_apogee_lockout) { + apogee_lockout_value.setSelectedItem(Integer.toString(new_apogee_lockout)); + } + + public int apogee_lockout() { + return Integer.parseInt(apogee_lockout_value.getSelectedItem().toString()); + } + + public void set_radio_frequency(double new_radio_frequency) { + int i; + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (f.close(new_radio_frequency)) { + radio_frequency_value.setSelectedIndex(i); + return; + } + } + for (i = 0; i < radio_frequency_value.getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) radio_frequency_value.getItemAt(i); + + if (new_radio_frequency < f.frequency) + break; + } + String description = String.format("%s serial %s", + product_value.getText(), + serial_value.getText()); + AltosFrequency new_frequency = new AltosFrequency(new_radio_frequency, description); + AltosUIPreferences.add_common_frequency(new_frequency); + radio_frequency_value.insertItemAt(new_frequency, i); + radio_frequency_value.setSelectedIndex(i); + } + + public double radio_frequency() { + return radio_frequency_value.frequency(); + } + + public void set_radio_calibration(int new_radio_calibration) { + radio_calibration_value.setText(String.format("%d", new_radio_calibration)); + } + + public int radio_calibration() { + return Integer.parseInt(radio_calibration_value.getText()); + } + + public void set_radio_enable(int new_radio_enable) { + if (new_radio_enable >= 0) { + radio_enable_value.setSelected(new_radio_enable > 0); + radio_enable_value.setEnabled(true); + } else { + radio_enable_value.setSelected(true); + radio_enable_value.setEnabled(false); + } + set_radio_enable_tool_tip(); + } + + public int radio_enable() { + if (radio_enable_value.isEnabled()) + return radio_enable_value.isSelected() ? 1 : 0; + else + return -1; + } + + public void set_callsign(String new_callsign) { + callsign_value.setText(new_callsign); + } + + public String callsign() { + return callsign_value.getText(); + } + + public void set_flight_log_max(int new_flight_log_max) { + if (new_flight_log_max == 0) + flight_log_max_value.setEnabled(false); + flight_log_max_value.setSelectedItem(Integer.toString(new_flight_log_max)); + set_flight_log_max_tool_tip(); + } + + public void set_flight_log_max_enabled(boolean enable) { + flight_log_max_value.setEnabled(enable); + set_flight_log_max_tool_tip(); + } + + public int flight_log_max() { + return Integer.parseInt(flight_log_max_value.getSelectedItem().toString()); + } + + public void set_flight_log_max_limit(int flight_log_max_limit) { + boolean any_added = false; + flight_log_max_value.removeAllItems(); + for (int i = 0; i < flight_log_max_values.length; i++) { + if (Integer.parseInt(flight_log_max_values[i]) < flight_log_max_limit){ + flight_log_max_value.addItem(flight_log_max_values[i]); + any_added = true; + } + } + flight_log_max_value.addItem(String.format("%d", flight_log_max_limit)); + } + + public void set_ignite_mode(int new_ignite_mode) { + if (new_ignite_mode >= ignite_mode_values.length) + new_ignite_mode = 0; + if (new_ignite_mode < 0) { + ignite_mode_value.setEnabled(false); + new_ignite_mode = 0; + } else { + ignite_mode_value.setEnabled(true); + } + ignite_mode_value.setSelectedIndex(new_ignite_mode); + set_ignite_mode_tool_tip(); + } + + public int ignite_mode() { + if (ignite_mode_value.isEnabled()) + return ignite_mode_value.getSelectedIndex(); + else + return -1; + } + + + public void set_pad_orientation(int new_pad_orientation) { + if (new_pad_orientation >= pad_orientation_values.length) + new_pad_orientation = 0; + if (new_pad_orientation < 0) { + pad_orientation_value.setEnabled(false); + new_pad_orientation = 0; + } else { + pad_orientation_value.setEnabled(true); + } + pad_orientation_value.setSelectedIndex(new_pad_orientation); + set_pad_orientation_tool_tip(); + } + + public int pad_orientation() { + if (pad_orientation_value.isEnabled()) + return pad_orientation_value.getSelectedIndex(); + else + return -1; + } + + public void set_clean() { + dirty = false; + } +} diff --git a/altosui/AltosConfigureUI.java b/altosui/AltosConfigureUI.java new file mode 100644 index 00000000..da82e8e0 --- /dev/null +++ b/altosui/AltosConfigureUI.java @@ -0,0 +1,416 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import javax.swing.plaf.basic.*; +import org.altusmetrum.AltosLib.*; + +class DelegatingRenderer implements ListCellRenderer { + + // ... + public static void install(JComboBox comboBox) { + DelegatingRenderer renderer = new DelegatingRenderer(comboBox); + renderer.initialise(); + comboBox.setRenderer(renderer); + } + + // ... + private final JComboBox comboBox; + + // ... + private ListCellRenderer delegate; + + // ... + private DelegatingRenderer(JComboBox comboBox) { + this.comboBox = comboBox; + } + + // ... + private void initialise() { + delegate = new JComboBox().getRenderer(); + comboBox.addPropertyChangeListener("UI", new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + delegate = new JComboBox().getRenderer(); + } + }); + } + + // ... + public Component getListCellRendererComponent(JList list, + Object value, int index, boolean isSelected, boolean cellHasFocus) { + + return delegate.getListCellRendererComponent(list, + ((UIManager.LookAndFeelInfo) value).getName(), + index, isSelected, cellHasFocus); + } +} + +public class AltosConfigureUI + extends AltosDialog + implements DocumentListener +{ + JFrame owner; + AltosVoice voice; + Container pane; + + JRadioButton enable_voice; + JButton test_voice; + JButton close; + + JButton configure_log; + JTextField log_directory; + + JLabel callsign_label; + JTextField callsign_value; + + JRadioButton imperial_units; + + JLabel font_size_label; + JComboBox font_size_value; + + JLabel look_and_feel_label; + JComboBox look_and_feel_value; + + JRadioButton serial_debug; + + JButton manage_bluetooth; + JButton manage_frequencies; + + final static String[] font_size_names = { "Small", "Medium", "Large" }; + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + AltosUIPreferences.set_callsign(callsign_value.getText()); + } + + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public AltosConfigureUI(JFrame in_owner, AltosVoice in_voice) { + super(in_owner, "Configure AltosUI", false); + + GridBagConstraints c; + + Insets insets = new Insets(4, 4, 4, 4); + + int row = 0; + + owner = in_owner; + voice = in_voice; + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.insets = insets; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + + /* Nice label at the top */ + c.gridx = 0; + c.gridy = row++; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + pane.add(new JLabel ("Configure AltOS UI"), c); + + c.gridx = 0; + c.gridy = row++; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + pane.add(new JLabel (String.format("AltOS version %s", AltosVersion.version)), c); + + /* Voice settings */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Voice"), c); + + enable_voice = new JRadioButton("Enable", AltosUIPreferences.voice()); + enable_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JRadioButton item = (JRadioButton) e.getSource(); + boolean enabled = item.isSelected(); + AltosUIPreferences.set_voice(enabled); + if (enabled) + voice.speak_always("Enable voice."); + else + voice.speak_always("Disable voice."); + } + }); + c.gridx = 1; + c.gridy = row; + c.gridwidth = 1; + c.weightx = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(enable_voice, c); + enable_voice.setToolTipText("Enable/Disable all audio in-flight announcements"); + + c.gridx = 2; + c.gridy = row++; + c.gridwidth = 1; + c.weightx = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + test_voice = new JButton("Test Voice"); + test_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + voice.speak("That's one small step for man; one giant leap for mankind."); + } + }); + pane.add(test_voice, c); + test_voice.setToolTipText("Play a stock audio clip to check volume"); + + /* Log directory settings */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Log Directory"), c); + + configure_log = new JButton(AltosUIPreferences.logdir().getPath()); + configure_log.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosUIPreferences.ConfigureLog(); + configure_log.setText(AltosUIPreferences.logdir().getPath()); + } + }); + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(configure_log, c); + configure_log.setToolTipText("Which directory flight logs are stored in"); + + /* Callsign setting */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Callsign"), c); + + callsign_value = new JTextField(AltosUIPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(callsign_value, c); + callsign_value.setToolTipText("Callsign sent in packet mode"); + + /* Imperial units setting */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Imperial Units"), c); + + imperial_units = new JRadioButton("Enable", AltosUIPreferences.serial_debug()); + imperial_units.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JRadioButton item = (JRadioButton) e.getSource(); + boolean enabled = item.isSelected(); + AltosUIPreferences.set_imperial_units(enabled); + } + }); + imperial_units.setToolTipText("Use Imperial units instead of metric"); + + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(imperial_units, c); + + /* Font size setting */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Font size"), c); + + font_size_value = new JComboBox(font_size_names); + int font_size = AltosUIPreferences.font_size(); + font_size_value.setSelectedIndex(font_size - Altos.font_size_small); + font_size_value.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int size = font_size_value.getSelectedIndex() + Altos.font_size_small; + + AltosUIPreferences.set_font_size(size); + } + }); + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(font_size_value, c); + font_size_value.setToolTipText("Font size used in telemetry window"); + + /* Look & Feel setting */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Look & feel"), c); + + class LookAndFeelRenderer extends BasicComboBoxRenderer implements ListCellRenderer { + + public LookAndFeelRenderer() { + super(); + } + + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) + { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + setText(((UIManager.LookAndFeelInfo) value).getName()); + return this; + } + } + + final UIManager.LookAndFeelInfo[] look_and_feels = UIManager.getInstalledLookAndFeels(); + + look_and_feel_value = new JComboBox(look_and_feels); + + DelegatingRenderer.install(look_and_feel_value); + + String look_and_feel = AltosUIPreferences.look_and_feel(); + for (int i = 0; i < look_and_feels.length; i++) + if (look_and_feel.equals(look_and_feels[i].getClassName())) + look_and_feel_value.setSelectedIndex(i); + + look_and_feel_value.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int id = look_and_feel_value.getSelectedIndex(); + + AltosUIPreferences.set_look_and_feel(look_and_feels[id].getClassName()); + } + }); + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(look_and_feel_value, c); + look_and_feel_value.setToolTipText("Look&feel used for new windows"); + + /* Serial debug setting */ + c.gridx = 0; + c.gridy = row; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Serial Debug"), c); + + serial_debug = new JRadioButton("Enable", AltosUIPreferences.serial_debug()); + serial_debug.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JRadioButton item = (JRadioButton) e.getSource(); + boolean enabled = item.isSelected(); + AltosUIPreferences.set_serial_debug(enabled); + } + }); + serial_debug.setToolTipText("Enable/Disable USB I/O getting sent to the console"); + + c.gridx = 1; + c.gridy = row++; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(serial_debug, c); + + manage_bluetooth = new JButton("Manage Bluetooth"); + manage_bluetooth.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosBTManage.show(owner, AltosBTKnown.bt_known()); + } + }); + c.gridx = 0; + c.gridy = row; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(manage_bluetooth, c); + + manage_frequencies = new JButton("Manage Frequencies"); + manage_frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosConfigFreqUI.show(owner); + } + }); + manage_frequencies.setToolTipText("Configure which values are shown in frequency menus"); + c.gridx = 2; + c.gridx = 2; + c.gridy = row++; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(manage_frequencies, c); + + /* And a close button at the bottom */ + close = new JButton("Close"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + c.gridx = 0; + c.gridy = row++; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + pane.add(close, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } +} diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java new file mode 100644 index 00000000..4bd51c39 --- /dev/null +++ b/altosui/AltosDataChooser.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import org.altusmetrum.AltosLib.*; + +public class AltosDataChooser extends JFileChooser { + JFrame frame; + String filename; + File file; + + public String filename() { + return filename; + } + + public File file() { + return file; + } + + public AltosRecordIterable runDialog() { + int ret; + + ret = showOpenDialog(frame); + if (ret == APPROVE_OPTION) { + file = getSelectedFile(); + if (file == null) + return null; + filename = file.getName(); + try { + if (filename.endsWith("eeprom")) { + FileInputStream in = new FileInputStream(file); + return new AltosEepromIterable(in); + } else if (filename.endsWith("telem")) { + FileInputStream in = new FileInputStream(file); + return new AltosTelemetryIterable(in); + } else if (filename.endsWith("mega")) { + FileInputStream in = new FileInputStream(file); + return new AltosEepromMegaIterable(in); + } else { + throw new FileNotFoundException(); + } + } catch (FileNotFoundException fe) { + JOptionPane.showMessageDialog(frame, + fe.getMessage(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } + } + return null; + } + + public AltosDataChooser(JFrame in_frame) { + frame = in_frame; + setDialogTitle("Select Flight Record File"); + setFileFilter(new FileNameExtensionFilter("Flight data file", + "telem", "eeprom", "mega")); + setCurrentDirectory(AltosUIPreferences.logdir()); + } +} diff --git a/altosui/AltosDataPoint.java b/altosui/AltosDataPoint.java new file mode 100644 index 00000000..5e077320 --- /dev/null +++ b/altosui/AltosDataPoint.java @@ -0,0 +1,30 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +interface AltosDataPoint { + int version(); + int serial(); + int flight(); + String callsign(); + double time(); + double rssi(); + + int state(); + String state_name(); + + double acceleration(); + double pressure(); + double altitude(); + double height(); + double accel_speed(); + double baro_speed(); + double temperature(); + double battery_voltage(); + double drogue_voltage(); + double main_voltage(); + boolean has_accel(); +} + diff --git a/altosui/AltosDataPointReader.java b/altosui/AltosDataPointReader.java new file mode 100644 index 00000000..821b0771 --- /dev/null +++ b/altosui/AltosDataPointReader.java @@ -0,0 +1,86 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.IOException; +import java.text.ParseException; +import java.lang.UnsupportedOperationException; +import java.util.NoSuchElementException; +import java.util.Iterator; +import org.altusmetrum.AltosLib.*; + +class AltosDataPointReader implements Iterable<AltosDataPoint> { + Iterator<AltosRecord> iter; + AltosState state; + AltosRecord record; + boolean has_gps; + boolean has_accel; + boolean has_ignite; + + final static int MISSING = AltosRecord.MISSING; + + public AltosDataPointReader(AltosRecordIterable reader) { + this.iter = reader.iterator(); + this.state = null; + has_accel = reader.has_accel(); + has_gps = reader.has_gps(); + has_ignite = reader.has_ignite(); + } + + private void read_next_record() + throws NoSuchElementException + { + record = iter.next(); + state = new AltosState(record, state); + } + + private AltosDataPoint current_dp() { + assert this.record != null; + + return new AltosDataPoint() { + public int version() { return record.version; } + public int serial() { return record.serial; } + public int flight() { return record.flight; } + public String callsign() { return record.callsign; } + public double time() { return record.time; } + public double rssi() { return record.rssi; } + + public int state() { return record.state; } + public String state_name() { return record.state(); } + + public double acceleration() { return record.acceleration(); } + public double pressure() { return record.raw_pressure(); } + public double altitude() { return record.raw_altitude(); } + public double height() { return record.raw_height(); } + public double accel_speed() { return record.accel_speed(); } + public double baro_speed() { return state.baro_speed; } + public double temperature() { return record.temperature(); } + public double battery_voltage() { return record.battery_voltage(); } + public double drogue_voltage() { return record.drogue_voltage(); } + public double main_voltage() { return record.main_voltage(); } + public boolean has_accel() { return has_accel; } + }; + } + + public Iterator<AltosDataPoint> iterator() { + return new Iterator<AltosDataPoint>() { + public void remove() { + throw new UnsupportedOperationException(); + } + public boolean hasNext() { + if (record != null && record.state == Altos.ao_flight_landed) + return false; + return iter.hasNext(); + } + public AltosDataPoint next() { + do { + read_next_record(); + } while (record.time < -1.0 && hasNext()); + return current_dp(); + } + }; + } +} + diff --git a/altosui/AltosDebug.java b/altosui/AltosDebug.java new file mode 100644 index 00000000..23e38bc0 --- /dev/null +++ b/altosui/AltosDebug.java @@ -0,0 +1,280 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosDebug extends AltosSerial { + + public static final byte WR_CONFIG = 0x1d; + public static final byte RD_CONFIG = 0x24; + public static final byte CONFIG_TIMERS_OFF = (1 << 3); + public static final byte CONFIG_DMA_PAUSE = (1 << 2); + public static final byte CONFIG_TIMER_SUSPEND = (1 << 1); + public static final byte SET_FLASH_INFO_PAGE = (1 << 0); + + public static final byte GET_PC = 0x28; + public static final byte READ_STATUS = 0x34; + public static final byte STATUS_CHIP_ERASE_DONE = (byte) (1 << 7); + public static final byte STATUS_PCON_IDLE = (1 << 6); + public static final byte STATUS_CPU_HALTED = (1 << 5); + public static final byte STATUS_POWER_MODE_0 = (1 << 4); + public static final byte STATUS_HALT_STATUS = (1 << 3); + public static final byte STATUS_DEBUG_LOCKED = (1 << 2); + public static final byte STATUS_OSCILLATOR_STABLE = (1 << 1); + public static final byte STATUS_STACK_OVERFLOW = (1 << 0); + + public static final byte SET_HW_BRKPNT = 0x3b; + public static byte HW_BRKPNT_N(byte n) { return (byte) ((n) << 3); } + public static final byte HW_BRKPNT_N_MASK = (0x3 << 3); + public static final byte HW_BRKPNT_ENABLE = (1 << 2); + + public static final byte HALT = 0x44; + public static final byte RESUME = 0x4c; + public static byte DEBUG_INSTR(byte n) { return (byte) (0x54|(n)); } + public static final byte STEP_INSTR = 0x5c; + public static byte STEP_REPLACE(byte n) { return (byte) (0x64|(n)); } + public static final byte GET_CHIP_ID = 0x68; + + + boolean debug_mode; + + void ensure_debug_mode() { + if (!debug_mode) { + printf("D\n"); + try { + flush_input(); + } catch (InterruptedException ie) { + } + debug_mode = true; + } + } + + void dump_memory(String header, int address, byte[] bytes, int start, int len) { + System.out.printf("%s\n", header); + for (int j = 0; j < len; j++) { + if ((j & 15) == 0) { + if (j != 0) + System.out.printf("\n"); + System.out.printf ("%04x:", address + j); + } + System.out.printf(" %02x", bytes[start + j]); + } + System.out.printf("\n"); + } + + /* + * Write target memory + */ + public void write_memory(int address, byte[] bytes, int start, int len) { + ensure_debug_mode(); +// dump_memory("write_memory", address, bytes, start, len); + printf("O %x %x\n", len, address); + for (int i = 0; i < len; i++) + printf("%02x", bytes[start + i]); + } + + public void write_memory(int address, byte[] bytes) { + write_memory(address, bytes, 0, bytes.length); + } + + /* + * Read target memory + */ + public byte[] read_memory(int address, int length) + throws IOException, InterruptedException { + byte[] data = new byte[length]; + + flush_input(); + ensure_debug_mode(); + printf("I %x %x\n", length, address); + int i = 0; + int start = 0; + while (i < length) { + String line = get_reply().trim(); + if (!Altos.ishex(line) || line.length() % 2 != 0) + throw new IOException( + String.format + ("Invalid reply \"%s\"", line)); + int this_time = line.length() / 2; + for (int j = 0; j < this_time; j++) + data[start + j] = (byte) ((Altos.fromhex(line.charAt(j*2)) << 4) + + Altos.fromhex(line.charAt(j*2+1))); + start += this_time; + i += this_time; + } +// dump_memory("read_memory", address, data, 0, length); + + return data; + } + + /* + * Write raw bytes to the debug link using the 'P' command + */ + public void write_bytes(byte[] bytes) throws IOException { + int i = 0; + ensure_debug_mode(); + while (i < bytes.length) { + int this_time = bytes.length - i; + if (this_time > 8) + this_time = 0; + printf("P"); + for (int j = 0; j < this_time; j++) + printf(" %02x", bytes[i+j]); + printf("\n"); + i += this_time; + } + } + + public void write_byte(byte b) throws IOException { + byte[] bytes = { b }; + write_bytes(bytes); + } + + /* + * Read raw bytes from the debug link using the 'G' command + */ + public byte[] read_bytes(int length) + throws IOException, InterruptedException { + + flush_input(); + ensure_debug_mode(); + printf("G %x\n", length); + int i = 0; + byte[] data = new byte[length]; + while (i < length) { + String line = get_reply(); + + if (line == null) + throw new IOException("Timeout in read_bytes"); + line = line.trim(); + String tokens[] = line.split("\\s+"); + for (int j = 0; j < tokens.length; j++) { + if (!Altos.ishex(tokens[j]) || + tokens[j].length() != 2) + throw new IOException( + String.format + ("Invalid read_bytes reply \"%s\"", line)); + try { + if (i + j >= length) + throw new IOException( + String.format + ("Invalid read_bytes reply \"%s\"", line)); + else + data[i + j] = (byte) Integer.parseInt(tokens[j], 16); + } catch (NumberFormatException ne) { + throw new IOException( + String.format + ("Invalid read_bytes reply \"%s\"", line)); + } + } + i += tokens.length; + } + return data; + } + + public byte read_byte() throws IOException, InterruptedException { + return read_bytes(1)[0]; + } + + public byte debug_instr(byte[] instruction) throws IOException, InterruptedException { + byte[] command = new byte[1 + instruction.length]; + command[0] = DEBUG_INSTR((byte) instruction.length); + for (int i = 0; i < instruction.length; i++) + command[i+1] = instruction[i]; + write_bytes(command); + return read_byte(); + } + + public byte resume() throws IOException, InterruptedException { + write_byte(RESUME); + return read_byte(); + } + + public int read_uint16() throws IOException, InterruptedException { + byte[] d = read_bytes(2); + return ((int) (d[0] & 0xff) << 8) | (d[1] & 0xff); + } + + public int read_uint8() throws IOException, InterruptedException { + byte[] d = read_bytes(1); + return (int) (d[0] & 0xff); + } + + public int get_chip_id() throws IOException, InterruptedException { + write_byte(GET_CHIP_ID); + return read_uint16(); + } + + public int get_pc() throws IOException, InterruptedException { + write_byte(GET_PC); + return read_uint16(); + } + + public byte read_status() throws IOException, InterruptedException { + write_byte(READ_STATUS); + return read_byte(); + } + + static final byte LJMP = 0x02; + + public void set_pc(int pc) throws IOException, InterruptedException { + byte high = (byte) (pc >> 8); + byte low = (byte) pc; + byte[] jump_mem = { LJMP, high, low }; + debug_instr(jump_mem); + } + + public boolean check_connection() throws IOException, InterruptedException { + byte reply = read_status(); + if ((reply & STATUS_CHIP_ERASE_DONE) == 0) + return false; + if ((reply & STATUS_PCON_IDLE) != 0) + return false; + if ((reply & STATUS_POWER_MODE_0) == 0) + return false; + return true; + } + + public AltosRomconfig romconfig() { + try { + byte[] bytes = read_memory(0xa0, 10); + return new AltosRomconfig(bytes, 0); + } catch (IOException ie) { + } catch (InterruptedException ie) { + } + return new AltosRomconfig(); + } + + /* + * Reset target + */ + public void reset() { + printf ("R\n"); + } + + public AltosDebug (AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + super(in_device); + } +}
\ No newline at end of file diff --git a/altosui/AltosDescent.java b/altosui/AltosDescent.java new file mode 100644 index 00000000..62258814 --- /dev/null +++ b/altosui/AltosDescent.java @@ -0,0 +1,440 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosDescent extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public abstract class DescentStatus { + JLabel label; + JTextField value; + AltosLights lights; + + abstract void show(AltosState state, int crc_errors); + + void show() { + label.setVisible(true); + value.setVisible(true); + lights.setVisible(true); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + lights.setVisible(false); + } + + void reset() { + value.setText(""); + lights.set(false); + } + + void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + public DescentStatus (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + lights = new AltosLights(); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(lights, c); + add(lights); + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.gridwidth = 3; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 4; c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + } + } + + public abstract class DescentValue { + JLabel label; + JTextField value; + + void reset() { + value.setText(""); + } + + abstract void show(AltosState state, int crc_errors); + + void show() { + label.setVisible(true); + value.setVisible(true); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + } + + void show(AltosUnits units, double v) { + value.setText(units.show(8, v)); + } + + void show(String format, double v) { + value.setText(String.format(format, v)); + } + + void show(String v) { + value.setText(v); + } + + void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + public DescentValue (GridBagLayout layout, int x, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = x + 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + add(label, c); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 2; c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + add(value, c); + } + } + + public abstract class DescentDualValue { + JLabel label; + JTextField value1; + JTextField value2; + + void reset() { + value1.setText(""); + value2.setText(""); + } + + void show() { + label.setVisible(true); + value1.setVisible(true); + value2.setVisible(true); + } + + void hide() { + label.setVisible(false); + value1.setVisible(false); + value2.setVisible(false); + } + + void set_font() { + label.setFont(Altos.label_font); + value1.setFont(Altos.value_font); + value2.setFont(Altos.value_font); + } + + abstract void show(AltosState state, int crc_errors); + + void show(String v1, String v2) { + show(); + value1.setText(v1); + value2.setText(v2); + } + void show(String f1, double v1, String f2, double v2) { + show(); + value1.setText(String.format(f1, v1)); + value2.setText(String.format(f2, v2)); + } + + public DescentDualValue (GridBagLayout layout, int x, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = x + 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value1 = new JTextField(Altos.text_width); + value1.setFont(Altos.value_font); + value1.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 2; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value1, c); + add(value1); + + value2 = new JTextField(Altos.text_width); + value2.setFont(Altos.value_font); + value2.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 4; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.gridwidth = 1; + layout.setConstraints(value2, c); + add(value2); + } + } + + class Height extends DescentValue { + void show (AltosState state, int crc_errors) { + show(AltosConvert.height, state.height); + } + public Height (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Height"); + } + } + + Height height; + + class Speed extends DescentValue { + void show (AltosState state, int crc_errors) { + double speed = state.speed; + if (!state.ascent) + speed = state.baro_speed; + show(AltosConvert.speed, speed); + } + public Speed (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Speed"); + } + } + + Speed speed; + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %d° %9.6f", h, deg, min); + } + + class Lat extends DescentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null && state.gps.connected) + show(pos(state.gps.lat,"N", "S")); + else + show("???"); + } + public Lat (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Latitude"); + } + } + + Lat lat; + + class Lon extends DescentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null && state.gps.connected) + show(pos(state.gps.lon,"W", "E")); + else + show("???"); + } + public Lon (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Longitude"); + } + } + + Lon lon; + + class Apogee extends DescentStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.drogue_sense)); + lights.set(state.drogue_sense > 3.2); + } + public Apogee (GridBagLayout layout, int y) { + super(layout, y, "Apogee Igniter Voltage"); + } + } + + Apogee apogee; + + class Main extends DescentStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.main_sense)); + lights.set(state.main_sense > 3.2); + } + public Main (GridBagLayout layout, int y) { + super(layout, y, "Main Igniter Voltage"); + } + } + + Main main; + + class Bearing extends DescentDualValue { + void show (AltosState state, int crc_errors) { + if (state.from_pad != null) { + show( String.format("%3.0f°", state.from_pad.bearing), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_LONG)); + } else { + show("???", "???"); + } + } + public Bearing (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Bearing"); + } + } + + Bearing bearing; + + class Range extends DescentValue { + void show (AltosState state, int crc_errors) { + show(AltosConvert.distance, state.range); + } + public Range (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Range"); + } + } + + Range range; + + class Elevation extends DescentValue { + void show (AltosState state, int crc_errors) { + show("%3.0f°", state.elevation); + } + public Elevation (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Elevation"); + } + } + + Elevation elevation; + + public void reset() { + lat.reset(); + lon.reset(); + height.reset(); + speed.reset(); + bearing.reset(); + range.reset(); + elevation.reset(); + main.reset(); + apogee.reset(); + } + + public void set_font() { + lat.set_font(); + lon.set_font(); + height.set_font(); + speed.set_font(); + bearing.set_font(); + range.set_font(); + elevation.set_font(); + main.set_font(); + apogee.set_font(); + } + + public void show(AltosState state, int crc_errors) { + height.show(state, crc_errors); + speed.show(state, crc_errors); + if (state.gps != null && state.gps.connected) { + bearing.show(state, crc_errors); + range.show(state, crc_errors); + elevation.show(state, crc_errors); + lat.show(state, crc_errors); + lon.show(state, crc_errors); + } else { + bearing.hide(); + range.hide(); + elevation.hide(); + lat.hide(); + lon.hide(); + } + if (state.main_sense != AltosRecord.MISSING) + main.show(state, crc_errors); + else + main.hide(); + if (state.drogue_sense != AltosRecord.MISSING) + apogee.show(state, crc_errors); + else + apogee.hide(); + } + + public AltosDescent() { + layout = new GridBagLayout(); + + setLayout(layout); + + /* Elements in descent display */ + speed = new Speed(layout, 0, 0); + height = new Height(layout, 2, 0); + elevation = new Elevation(layout, 0, 1); + range = new Range(layout, 2, 1); + bearing = new Bearing(layout, 0, 2); + lat = new Lat(layout, 0, 3); + lon = new Lon(layout, 2, 3); + + apogee = new Apogee(layout, 4); + main = new Main(layout, 5); + } +} diff --git a/altosui/AltosDevice.java b/altosui/AltosDevice.java new file mode 100644 index 00000000..1b5c1a91 --- /dev/null +++ b/altosui/AltosDevice.java @@ -0,0 +1,31 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; + +public interface AltosDevice { + public abstract String toString(); + public abstract String toShortString(); + public abstract int getSerial(); + public abstract String getPath(); + public abstract boolean matchProduct(int product); + public abstract String getErrorString(); + public SWIGTYPE_p_altos_file open(); +} diff --git a/altosui/AltosDeviceDialog.java b/altosui/AltosDeviceDialog.java new file mode 100644 index 00000000..fa9d0013 --- /dev/null +++ b/altosui/AltosDeviceDialog.java @@ -0,0 +1,188 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.util.*; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import libaltosJNI.*; + +public class AltosDeviceDialog extends AltosDialog implements ActionListener { + + private AltosDevice value; + private JList list; + private JButton cancel_button; + private JButton select_button; + private JButton manage_bluetooth_button; + private Frame frame; + private int product; + + private AltosDevice getValue() { + return value; + } + + private AltosDevice[] devices() { + java.util.List<AltosDevice> usb_devices = AltosUSBDevice.list(product); + int num_devices = usb_devices.size(); + java.util.List<AltosDevice> bt_devices = AltosBTKnown.bt_known().list(product); + num_devices += bt_devices.size(); + AltosDevice[] devices = new AltosDevice[num_devices]; + + for (int i = 0; i < usb_devices.size(); i++) + devices[i] = usb_devices.get(i); + int off = usb_devices.size(); + for (int j = 0; j < bt_devices.size(); j++) + devices[off + j] = bt_devices.get(j); + return devices; + } + + private void update_devices() { + AltosDevice[] devices = devices(); + list.setListData(devices); + select_button.setEnabled(devices.length > 0); + } + + private AltosDeviceDialog (Frame in_frame, Component location, int in_product) { + super(in_frame, "Device Selection", true); + + product = in_product; + frame = in_frame; + value = null; + + AltosDevice[] devices = devices(); + + cancel_button = new JButton("Cancel"); + cancel_button.setActionCommand("cancel"); + cancel_button.addActionListener(this); + + manage_bluetooth_button = new JButton("Manage Bluetooth"); + manage_bluetooth_button.setActionCommand("manage"); + manage_bluetooth_button.addActionListener(this); + + select_button = new JButton("Select"); + select_button.setActionCommand("select"); + select_button.addActionListener(this); + if (devices.length == 0) + select_button.setEnabled(false); + getRootPane().setDefaultButton(select_button); + + list = new JList(devices) { + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + }; + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + select_button.doClick(); //emulate button click + } + } + }); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(400, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to bottom. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); + + JLabel label = new JLabel("Select Device"); + label.setLabelFor(list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + //Lay out the buttons from left to right. + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(cancel_button); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(manage_bluetooth_button); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(select_button); + + //Put everything together, using the content pane's BorderLayout. + Container contentPane = getContentPane(); + contentPane.add(listPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + //Initialize values. + if (devices != null && devices.length != 0) + list.setSelectedValue(devices[0], true); + pack(); + setLocationRelativeTo(location); + } + + //Handle clicks on the Set and Cancel buttons. + public void actionPerformed(ActionEvent e) { + if ("select".equals(e.getActionCommand())) + value = (AltosDevice)(list.getSelectedValue()); + if ("manage".equals(e.getActionCommand())) { + AltosBTManage.show(frame, AltosBTKnown.bt_known()); + update_devices(); + return; + } + setVisible(false); + } + + public static AltosDevice show (Component frameComp, int product) { + + Frame frame = JOptionPane.getFrameForComponent(frameComp); + AltosDeviceDialog dialog; + + dialog = new AltosDeviceDialog(frame, frameComp, product); + dialog.setVisible(true); + return dialog.getValue(); + } +} diff --git a/altosui/AltosDialog.java b/altosui/AltosDialog.java new file mode 100644 index 00000000..ff38c3e4 --- /dev/null +++ b/altosui/AltosDialog.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +class AltosDialogListener extends WindowAdapter { + public void windowClosing (WindowEvent e) { + AltosUIPreferences.unregister_ui_listener((AltosDialog) e.getWindow()); + } +} + +public class AltosDialog extends JDialog implements AltosUIListener { + + public void ui_changed(String look_and_feel) { + SwingUtilities.updateComponentTreeUI(this); + this.pack(); + } + + public AltosDialog() { + AltosUIPreferences.register_ui_listener(this); + addWindowListener(new AltosDialogListener()); + } + + public AltosDialog(Frame frame, String label, boolean modal) { + super(frame, label, modal); + AltosUIPreferences.register_ui_listener(this); + addWindowListener(new AltosDialogListener()); + } + + public AltosDialog(Frame frame, boolean modal) { + super(frame, modal); + AltosUIPreferences.register_ui_listener(this); + addWindowListener(new AltosDialogListener()); + } +} diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java new file mode 100644 index 00000000..cf69c414 --- /dev/null +++ b/altosui/AltosDisplayThread.java @@ -0,0 +1,279 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosDisplayThread extends Thread { + + Frame parent; + IdleThread idle_thread; + AltosVoice voice; + AltosFlightReader reader; + int crc_errors; + AltosFlightDisplay display; + + void show_internal(AltosState state, int crc_errors) { + if (state != null) + display.show(state, crc_errors); + } + + void show_safely(AltosState in_state, int in_crc_errors) { + final AltosState state = in_state; + final int crc_errors = in_crc_errors; + Runnable r = new Runnable() { + public void run() { + try { + show_internal(state, crc_errors); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + void reading_error_internal() { + JOptionPane.showMessageDialog(parent, + String.format("Error reading from \"%s\"", reader.name), + "Telemetry Read Error", + JOptionPane.ERROR_MESSAGE); + } + + void reading_error_safely() { + Runnable r = new Runnable() { + public void run() { + try { + reading_error_internal(); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + class IdleThread extends Thread { + + boolean started; + private AltosState state; + int reported_landing; + int report_interval; + long report_time; + + public synchronized void report(boolean last) { + if (state == null) + return; + + /* reset the landing count once we hear about a new flight */ + if (state.state < Altos.ao_flight_drogue) + reported_landing = 0; + + /* Shut up once the rocket is on the ground */ + if (reported_landing > 2) { + return; + } + + /* If the rocket isn't on the pad, then report height */ + if (Altos.ao_flight_drogue <= state.state && + state.state < Altos.ao_flight_landed && + state.range >= 0) + { + voice.speak("Height %s, bearing %s %d, elevation %d, range %s.\n", + AltosConvert.height.say(state.height), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_VOICE), + (int) (state.from_pad.bearing + 0.5), + (int) (state.elevation + 0.5), + AltosConvert.distance.say(state.range)); + } else if (state.state > Altos.ao_flight_pad) { + voice.speak(AltosConvert.height.say_units(state.height)); + } else { + reported_landing = 0; + } + + /* If the rocket is coming down, check to see if it has landed; + * either we've got a landed report or we haven't heard from it in + * a long time + */ + if (state.state >= Altos.ao_flight_drogue && + (last || + System.currentTimeMillis() - state.report_time >= 15000 || + state.state == Altos.ao_flight_landed)) + { + if (Math.abs(state.baro_speed) < 20 && state.height < 100) + voice.speak("rocket landed safely"); + else + voice.speak("rocket may have crashed"); + if (state.from_pad != null) + voice.speak("Bearing %d degrees, range %s.", + (int) (state.from_pad.bearing + 0.5), + AltosConvert.distance.say_units(state.from_pad.distance)); + ++reported_landing; + if (state.state != Altos.ao_flight_landed) { + state.state = Altos.ao_flight_landed; + show_safely(state, 0); + } + } + } + + long now () { + return System.currentTimeMillis(); + } + + void set_report_time() { + report_time = now() + report_interval; + } + + public void run () { + try { + for (;;) { + set_report_time(); + for (;;) { + voice.drain(); + synchronized (this) { + long sleep_time = report_time - now(); + if (sleep_time <= 0) + break; + wait(sleep_time); + } + } + report(false); + } + } catch (InterruptedException ie) { + try { + voice.drain(); + } catch (InterruptedException iie) { } + } + } + + public synchronized void notice(AltosState new_state, boolean spoken) { + AltosState old_state = state; + state = new_state; + if (!started && state.state > Altos.ao_flight_pad) { + started = true; + start(); + } + + if (state.state < Altos.ao_flight_drogue) + report_interval = 10000; + else + report_interval = 20000; + if (old_state != null && old_state.state != state.state) { + report_time = now(); + this.notify(); + } else if (spoken) + set_report_time(); + } + + public IdleThread() { + state = null; + reported_landing = 0; + report_interval = 10000; + } + } + + boolean tell(AltosState state, AltosState old_state) { + boolean ret = false; + if (old_state == null || old_state.state != state.state) { + voice.speak(state.data.state()); + if ((old_state == null || old_state.state <= Altos.ao_flight_boost) && + state.state > Altos.ao_flight_boost) { + voice.speak("max speed: %s.", + AltosConvert.speed.say_units(state.max_speed + 0.5)); + ret = true; + } else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) && + state.state >= Altos.ao_flight_drogue) { + voice.speak("max height: %s.", + AltosConvert.height.say_units(state.max_height + 0.5)); + ret = true; + } + } + if (old_state == null || old_state.gps_ready != state.gps_ready) { + if (state.gps_ready) { + voice.speak("GPS ready"); + ret = true; + } + else if (old_state != null) { + voice.speak("GPS lost"); + ret = true; + } + } + old_state = state; + return ret; + } + + public void run() { + boolean interrupted = false; + String line; + AltosState state = null; + AltosState old_state = null; + boolean told; + + idle_thread = new IdleThread(); + + try { + for (;;) { + try { + AltosRecord record = reader.read(); + if (record == null) + break; + old_state = state; + state = new AltosState(record, state); + reader.update(state); + show_safely(state, crc_errors); + told = tell(state, old_state); + idle_thread.notice(state, told); + } catch (ParseException pp) { + System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage()); + } catch (AltosCRCException ce) { + ++crc_errors; + show_safely(state, crc_errors); + } + } + } catch (InterruptedException ee) { + interrupted = true; + } catch (IOException ie) { + reading_error_safely(); + } finally { + if (!interrupted) + idle_thread.report(true); + reader.close(interrupted); + idle_thread.interrupt(); + try { + idle_thread.join(); + } catch (InterruptedException ie) {} + } + } + + public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) { + parent = in_parent; + voice = in_voice; + display = in_display; + reader = in_reader; + display.reset(); + } +} diff --git a/altosui/AltosEepromDelete.java b/altosui/AltosEepromDelete.java new file mode 100644 index 00000000..73f3a00f --- /dev/null +++ b/altosui/AltosEepromDelete.java @@ -0,0 +1,151 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosEepromDelete implements Runnable { + AltosEepromList flights; + Thread eeprom_thread; + AltosSerial serial_line; + boolean remote; + JFrame frame; + ActionListener listener; + boolean success; + + private void DeleteLog (AltosEepromLog log) + throws IOException, InterruptedException, TimeoutException { + + if (flights.config_data.flight_log_max != 0 || flights.config_data.log_format != 0) { + + /* Devices with newer firmware can erase the + * flash blocks containing each flight + */ + serial_line.flush_input(); + serial_line.printf("d %d\n", log.flight); + for (;;) { + /* It can take a while to erase the flash... */ + String line = serial_line.get_reply(20000); + if (line == null) + throw new TimeoutException(); + if (line.equals("Erased")) + break; + if (line.startsWith("No such")) + throw new IOException(line); + } + } + } + + private void show_error_internal(String message, String title) { + JOptionPane.showMessageDialog(frame, + message, + title, + JOptionPane.ERROR_MESSAGE); + } + + private void show_error(String in_message, String in_title) { + final String message = in_message; + final String title = in_title; + Runnable r = new Runnable() { + public void run() { + try { + show_error_internal(message, title); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + success = false; + try { + if (remote) + serial_line.start_remote(); + + for (AltosEepromLog log : flights) { + if (log.selected) { + DeleteLog(log); + } + } + success = true; + } catch (IOException ee) { + show_error (ee.getLocalizedMessage(), + serial_line.device.toShortString()); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + show_error (String.format("Connection to \"%s\" failed", + serial_line.device.toShortString()), + "Connection Failed"); + } finally { + try { + if (remote) + serial_line.stop_remote(); + } catch (InterruptedException ie) { + } finally { + serial_line.flush_output(); + serial_line.close(); + } + } + if (listener != null) { + Runnable r = new Runnable() { + public void run() { + try { + listener.actionPerformed(new ActionEvent(this, + success ? 1 : 0, + "delete")); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + } + + public void start() { + eeprom_thread = new Thread(this); + eeprom_thread.start(); + } + + public void addActionListener(ActionListener l) { + listener = l; + } + + public AltosEepromDelete(JFrame given_frame, + AltosSerial given_serial_line, + boolean given_remote, + AltosEepromList given_flights) { + frame = given_frame; + serial_line = given_serial_line; + remote = given_remote; + flights = given_flights; + success = false; + } +}
\ No newline at end of file diff --git a/altosui/AltosEepromDownload.java b/altosui/AltosEepromDownload.java new file mode 100644 index 00000000..b04890cd --- /dev/null +++ b/altosui/AltosEepromDownload.java @@ -0,0 +1,495 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosEepromDownload implements Runnable { + + JFrame frame; + AltosSerial serial_line; + boolean remote; + Thread eeprom_thread; + AltosEepromMonitor monitor; + + int flight; + int serial; + int year, month, day; + boolean want_file; + FileWriter eeprom_file; + LinkedList<String> eeprom_pending; + + AltosEepromList flights; + ActionListener listener; + boolean success; + ParseException parse_exception; + String extension; + + private void FlushPending() throws IOException { + for (String s : flights.config_data) { + eeprom_file.write(s); + eeprom_file.write('\n'); + } + + for (String s : eeprom_pending) + eeprom_file.write(s); + } + + private void CheckFile(boolean force) throws IOException { + if (eeprom_file != null) + return; + if (force || (flight != 0 && want_file)) { + AltosFile eeprom_name; + + if (extension == null) + extension = "data"; + if (year != 0 && month != 0 && day != 0) + eeprom_name = new AltosFile(year, month, day, serial, flight, extension); + else + eeprom_name = new AltosFile(serial, flight, extension); + + eeprom_file = new FileWriter(eeprom_name); + if (eeprom_file != null) { + monitor.set_file(eeprom_name.getName()); + FlushPending(); + eeprom_pending = null; + } + } + } + + void Log(AltosEepromRecord r) throws IOException { + if (r.cmd != Altos.AO_LOG_INVALID) { + String log_line = String.format("%c %4x %4x %4x\n", + r.cmd, r.tick, r.a, r.b); + if (eeprom_file != null) + eeprom_file.write(log_line); + else + eeprom_pending.add(log_line); + } + } + + void set_serial(int in_serial) { + serial = in_serial; + monitor.set_serial(serial); + } + + void set_flight(int in_flight) { + flight = in_flight; + monitor.set_flight(flight); + } + + boolean done; + int state; + + void CaptureFull(AltosEepromChunk eechunk) throws IOException { + boolean any_valid = false; + + extension = "eeprom"; + set_serial(flights.config_data.serial); + for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromRecord.record_length) { + try { + AltosEepromRecord r = new AltosEepromRecord(eechunk, i); + if (r.cmd == Altos.AO_LOG_FLIGHT) + set_flight(r.b); + + /* Monitor state transitions to update display */ + if (r.cmd == Altos.AO_LOG_STATE && r.a <= Altos.ao_flight_landed) { + state = r.a; + if (state > Altos.ao_flight_pad) + want_file = true; + } + + if (r.cmd == Altos.AO_LOG_GPS_DATE) { + year = 2000 + (r.a & 0xff); + month = (r.a >> 8) & 0xff; + day = (r.b & 0xff); + want_file = true; + } + if (r.cmd == Altos.AO_LOG_STATE && r.a == Altos.ao_flight_landed) + done = true; + any_valid = true; + Log(r); + } catch (ParseException pe) { + if (parse_exception == null) + parse_exception = pe; + } + } + + if (!any_valid) + done = true; + + CheckFile(false); + } + + boolean start; + int tiny_tick; + + void CaptureTiny (AltosEepromChunk eechunk) throws IOException { + boolean any_valid = false; + + extension = "eeprom"; + set_serial(flights.config_data.serial); + for (int i = 0; i < eechunk.data.length && !done; i += 2) { + int v = eechunk.data16(i); + AltosEepromRecord r; + + if (i == 0 && start) { + tiny_tick = 0; + start = false; + set_flight(v); + r = new AltosEepromRecord(Altos.AO_LOG_FLIGHT, tiny_tick, 0, v); + any_valid = true; + } else { + int s = v ^ 0x8000; + + if (Altos.ao_flight_startup <= s && s <= Altos.ao_flight_invalid) { + state = s; + r = new AltosEepromRecord(Altos.AO_LOG_STATE, tiny_tick, state, 0); + if (state == Altos.ao_flight_landed) + done = true; + state = s; + any_valid = true; + } else { + if (v != 0xffff) + any_valid = true; + + r = new AltosEepromRecord(Altos.AO_LOG_PRESSURE, tiny_tick, 0, v); + + /* + * The flight software records ascent data every 100ms, and descent + * data every 1s. + */ + if (state < Altos.ao_flight_drogue) + tiny_tick += 10; + else + tiny_tick += 100; + } + } + Log(r); + } + CheckFile(false); + if (!any_valid) + done = true; + } + + void LogTeleScience(AltosEepromTeleScience r) throws IOException { + if (r.type != Altos.AO_LOG_INVALID) { + String log_line = String.format("%c %4x %4x %d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d\n", + r.type, r.tick, r.tm_tick, r.tm_state, + r.data[0], r.data[1], r.data[2], r.data[3], + r.data[4], r.data[5], r.data[6], r.data[7], + r.data[8], r.data[9], r.data[10], r.data[11]); + if (eeprom_file != null) + eeprom_file.write(log_line); + else + eeprom_pending.add(log_line); + } + } + + boolean telescience_start; + + void CaptureTeleScience (AltosEepromChunk eechunk) throws IOException { + boolean any_valid = false; + + extension = "science"; + for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromTeleScience.record_length) { + try { + AltosEepromTeleScience r = new AltosEepromTeleScience(eechunk, i); + if (r.type == AltosEepromTeleScience.AO_LOG_TELESCIENCE_START) { + if (telescience_start) { + done = true; + break; + } + set_serial(r.data[0]); + set_flight(r.data[1]); + telescience_start = true; + } else { + if (!telescience_start) + break; + } + state = r.tm_state; + want_file =true; + any_valid = true; + LogTeleScience(r); + } catch (ParseException pe) { + if (parse_exception == null) + parse_exception = pe; + } + } + + CheckFile(false); + if (!any_valid) + done = true; + } + + void LogMega(AltosEepromMega r) throws IOException { + if (r.cmd != Altos.AO_LOG_INVALID) { + String log_line = String.format("%c %4x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x %2x\n", + r.cmd, r.tick, + r.data8[0], r.data8[1], r.data8[2], r.data8[3], + r.data8[4], r.data8[5], r.data8[6], r.data8[7], + r.data8[8], r.data8[9], r.data8[10], r.data8[11], + r.data8[12], r.data8[13], r.data8[14], r.data8[15], + r.data8[16], r.data8[17], r.data8[18], r.data8[19], + r.data8[20], r.data8[21], r.data8[22], r.data8[23], + r.data8[24], r.data8[25], r.data8[26], r.data8[27]); + if (eeprom_file != null) + eeprom_file.write(log_line); + else + eeprom_pending.add(log_line); + } + } + + void CaptureMega(AltosEepromChunk eechunk) throws IOException { + boolean any_valid = false; + + extension = "mega"; + set_serial(flights.config_data.serial); + for (int i = 0; i < eechunk.chunk_size && !done; i += AltosEepromMega.record_length) { + try { + AltosEepromMega r = new AltosEepromMega(eechunk, i); + if (r.cmd == Altos.AO_LOG_FLIGHT) + set_flight(r.data16(0)); + + /* Monitor state transitions to update display */ + if (r.cmd == Altos.AO_LOG_STATE && r.data16(0) <= Altos.ao_flight_landed) { + state = r.data16(0); + if (state > Altos.ao_flight_pad) + want_file = true; + } + + if (r.cmd == Altos.AO_LOG_GPS_TIME) { + year = 2000 + r.data8(14); + month = r.data8(15); + day = r.data8(14); + want_file = true; + } + + if (r.cmd == Altos.AO_LOG_STATE && r.data16(0) == Altos.ao_flight_landed) + done = true; + any_valid = true; + LogMega(r); + } catch (ParseException pe) { + if (parse_exception == null) + parse_exception = pe; + } + } + if (!any_valid) + done = true; + + CheckFile(false); + } + + void CaptureTelemetry(AltosEepromChunk eechunk) throws IOException { + + } + + void CaptureLog(AltosEepromLog log) throws IOException, InterruptedException, TimeoutException { + int block, state_block = 0; + int log_format = flights.config_data.log_format; + + state = 0; + done = false; + start = true; + + if (flights.config_data.serial == 0) + throw new IOException("no serial number found"); + + /* Reset per-capture variables */ + flight = 0; + year = 0; + month = 0; + day = 0; + want_file = false; + eeprom_file = null; + eeprom_pending = new LinkedList<String>(); + + /* Set serial number in the monitor dialog window */ + /* Now scan the eeprom, reading blocks of data and converting to .eeprom file form */ + + state = 0; state_block = log.start_block; + for (block = log.start_block; !done && block < log.end_block; block++) { + monitor.set_value(AltosLib.state_name(state), state, block - state_block); + + AltosEepromChunk eechunk = new AltosEepromChunk(serial_line, block, block == log.start_block); + + /* + * Guess what kind of data is there if the device + * didn't tell us + */ + + if (log_format == Altos.AO_LOG_FORMAT_UNKNOWN) { + if (block == log.start_block) { + if (eechunk.data(0) == Altos.AO_LOG_FLIGHT) + log_format = Altos.AO_LOG_FORMAT_FULL; + else + log_format = Altos.AO_LOG_FORMAT_TINY; + } + } + + switch (log_format) { + case AltosLib.AO_LOG_FORMAT_FULL: + extension = "eeprom"; + CaptureFull(eechunk); + break; + case AltosLib.AO_LOG_FORMAT_TINY: + extension = "eeprom"; + CaptureTiny(eechunk); + break; + case AltosLib.AO_LOG_FORMAT_TELEMETRY: + extension = "telem"; + CaptureTelemetry(eechunk); + break; + case AltosLib.AO_LOG_FORMAT_TELESCIENCE: + extension = "science"; + CaptureTeleScience(eechunk); + break; + case AltosLib.AO_LOG_FORMAT_MEGAMETRUM: + extension = "mega"; + CaptureMega(eechunk); + } + } + CheckFile(true); + if (eeprom_file != null) { + eeprom_file.flush(); + eeprom_file.close(); + } + } + + private void show_message_internal(String message, String title, int message_type) { + JOptionPane.showMessageDialog(frame, + message, + title, + message_type); + } + + private void show_message(String in_message, String in_title, int in_message_type) { + final String message = in_message; + final String title = in_title; + final int message_type = in_message_type; + Runnable r = new Runnable() { + public void run() { + try { + show_message_internal(message, title, message_type); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + try { + boolean failed = false; + if (remote) + serial_line.start_remote(); + + for (AltosEepromLog log : flights) { + parse_exception = null; + if (log.selected) { + monitor.reset(); + CaptureLog(log); + } + if (parse_exception != null) { + failed = true; + show_message(String.format("Flight %d download error\n%s\nValid log data saved", + log.flight, + parse_exception.getMessage()), + serial_line.device.toShortString(), + JOptionPane.WARNING_MESSAGE); + } + } + success = !failed; + } catch (IOException ee) { + show_message(ee.getLocalizedMessage(), + serial_line.device.toShortString(), + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + System.out.printf("download interrupted\n"); + } catch (TimeoutException te) { + show_message(String.format("Connection to \"%s\" failed", + serial_line.device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } finally { + if (remote) { + try { + serial_line.stop_remote(); + } catch (InterruptedException ie) { + } + } + serial_line.flush_output(); + } + monitor.done(); + if (listener != null) { + Runnable r = new Runnable() { + public void run() { + try { + listener.actionPerformed(new ActionEvent(this, + success ? 1 : 0, + "download")); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + } + + public void start() { + eeprom_thread = new Thread(this); + eeprom_thread.start(); + } + + public void addActionListener(ActionListener l) { + listener = l; + } + + public AltosEepromDownload(JFrame given_frame, + AltosSerial given_serial_line, + boolean given_remote, + AltosEepromList given_flights) { + + frame = given_frame; + serial_line = given_serial_line; + serial_line.set_frame(frame); + remote = given_remote; + flights = given_flights; + success = false; + + monitor = new AltosEepromMonitor(frame, Altos.ao_flight_boost, Altos.ao_flight_landed); + monitor.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (eeprom_thread != null) + eeprom_thread.interrupt(); + } + }); + } +} diff --git a/altosui/AltosEepromList.java b/altosui/AltosEepromList.java new file mode 100644 index 00000000..6a656215 --- /dev/null +++ b/altosui/AltosEepromList.java @@ -0,0 +1,126 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +/* + * Temporary structure to hold the list of stored flights; + * each of these will be queried in turn to generate more + * complete information + */ + +class AltosEepromFlight { + int flight; + int start; + int end; + + public AltosEepromFlight(int in_flight, int in_start, int in_end) { + flight = in_flight; + start = in_start; + end = in_end; + } +} + +/* + * Construct a list of flights available in a connected device + */ + +public class AltosEepromList extends ArrayList<AltosEepromLog> { + AltosConfigData config_data; + + public AltosEepromList (AltosSerial serial_line, boolean remote) + throws IOException, InterruptedException, TimeoutException + { + try { + if (remote) + serial_line.start_remote(); + config_data = new AltosConfigData (serial_line); +// if (config_data.serial == 0) +// throw new IOException("no serial number found"); + + ArrayList<AltosEepromFlight> flights = new ArrayList<AltosEepromFlight>(); + + if (config_data.flight_log_max != 0 || config_data.log_format != 0) { + + /* Devices with newer firmware will support the 'l' + * command which will list the region of storage + * occupied by each available flight + */ + serial_line.printf("l\n"); + for (;;) { + String line = serial_line.get_reply(5000); + if (line == null) + throw new TimeoutException(); + if (line.contains("done")) + break; + if (line.contains("Syntax")) + continue; + String[] tokens = line.split("\\s+"); + if (tokens.length < 6) + break; + + int flight = -1, start = -1, end = -1; + try { + if (tokens[0].equals("flight")) + flight = AltosParse.parse_int(tokens[1]); + if (tokens[2].equals("start")) + start = AltosParse.parse_hex(tokens[3]); + if (tokens[4].equals("end")) + end = AltosParse.parse_hex(tokens[5]); + if (flight > 0 && start >= 0 && end > 0) + flights.add(new AltosEepromFlight(flight, start, end)); + } catch (ParseException pe) { System.out.printf("Parse error %s\n", pe.toString()); } + } + } else { + + /* Older devices will hold only a single + * flight. This also assumes that any older + * device will have a 1MB flash device + */ + flights.add(new AltosEepromFlight(0, 0, 0xfff)); + } + + /* With the list of flights collected, collect more complete + * information on them by reading the first block or two of + * data. This will add GPS coordinates and a date. For older + * firmware, this will also extract the flight number. + */ + for (AltosEepromFlight flight : flights) { + add(new AltosEepromLog(config_data, serial_line, + flight.flight, flight.start, flight.end)); + } + } finally { + if (remote) + serial_line.stop_remote(); + serial_line.flush_output(); + } + } +}
\ No newline at end of file diff --git a/altosui/AltosEepromManage.java b/altosui/AltosEepromManage.java new file mode 100644 index 00000000..563c90b3 --- /dev/null +++ b/altosui/AltosEepromManage.java @@ -0,0 +1,237 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosEepromManage implements ActionListener { + + JFrame frame; + boolean remote; + AltosDevice device; + AltosSerial serial_line; + AltosEepromList flights; + AltosEepromDownload download; + AltosEepromDelete delete; + + public void finish() { + if (serial_line != null) { + try { + serial_line.flush_input(); + } catch (InterruptedException ie) { + } + serial_line.close(); + serial_line = null; + } + } + + private String showDeletedFlights() { + String result = ""; + + for (AltosEepromLog flight : flights) { + if (flight.selected) { + if (result.equals("")) + result = String.format("%d", flight.flight); + else + result = String.format("%s, %d", result, flight.flight); + } + } + return result; + } + + public boolean download_done() { + AltosEepromSelect select = new AltosEepromSelect(frame, flights, "Delete"); + + if (select.run()) { + boolean any_selected = false; + for (AltosEepromLog flight : flights) + any_selected = any_selected || flight.selected; + if (any_selected) { + delete = new AltosEepromDelete(frame, + serial_line, + remote, + flights); + delete.addActionListener(this); + /* + * Start flight log delete + */ + + delete.start(); + return true; + } + } + return false; + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + boolean success = e.getID() != 0; + boolean running = false; + + if (cmd.equals("download")) { + if (success) + running = download_done(); + } else if (cmd.equals("delete")) { + if (success) { + JOptionPane.showMessageDialog(frame, + String.format("Flights erased: %s", + showDeletedFlights()), + serial_line.device.toShortString(), + JOptionPane.INFORMATION_MESSAGE); + } + } + if (!running) + finish(); + } + + public void got_flights(AltosEepromList in_flights) { + boolean running = false;; + + flights = in_flights; + try { + if (flights.size() == 0) { + JOptionPane.showMessageDialog(frame, + String.format("No flights available on %d", + device.getSerial()), + serial_line.device.toShortString(), + JOptionPane.INFORMATION_MESSAGE); + } else { + AltosEepromSelect select = new AltosEepromSelect(frame, flights, "Download"); + + if (select.run()) { + boolean any_selected = false; + for (AltosEepromLog flight : flights) + any_selected = any_selected || flight.selected; + if (any_selected) { + download = new AltosEepromDownload(frame, + serial_line, + remote, + flights); + download.addActionListener(this); + /* + * Start flight log download + */ + + download.start(); + running = true; + } else { + running = download_done(); + } + } + } + if (!running) + finish(); + } catch (Exception e) { + got_exception(e); + } + } + + public void got_exception(Exception e) { + if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(frame, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof TimeoutException) { + TimeoutException te = (TimeoutException) e; + JOptionPane.showMessageDialog(frame, + String.format("Communications failed with \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } + finish(); + } + + class EepromGetList implements Runnable { + + AltosEepromManage manage; + + public void run () { + Runnable r; + try { + flights = new AltosEepromList(serial_line, remote); + r = new Runnable() { + public void run() { + got_flights(flights); + } + }; + } catch (Exception e) { + final Exception f_e = e; + r = new Runnable() { + public void run() { + got_exception(f_e); + } + }; + } + SwingUtilities.invokeLater(r); + } + + public EepromGetList(AltosEepromManage in_manage) { + manage = in_manage; + } + } + + public AltosEepromManage(JFrame given_frame) { + + boolean running = false; + + frame = given_frame; + device = AltosDeviceDialog.show(frame, Altos.product_any); + + remote = false; + + if (device != null) { + try { + serial_line = new AltosSerial(device); + if (device.matchProduct(Altos.product_basestation)) + remote = true; + + serial_line.set_frame(frame); + + EepromGetList get_list = new EepromGetList(this); + Thread t = new Thread(get_list); + t.start(); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(frame, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(frame, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/altosui/AltosEepromMonitor.java b/altosui/AltosEepromMonitor.java new file mode 100644 index 00000000..75643442 --- /dev/null +++ b/altosui/AltosEepromMonitor.java @@ -0,0 +1,261 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosEepromMonitor extends AltosDialog { + + Container pane; + Box box; + JLabel serial_label; + JLabel flight_label; + JLabel file_label; + JLabel serial_value; + JLabel flight_value; + JLabel file_value; + JButton cancel; + JProgressBar pbar; + int min_state, max_state; + + public AltosEepromMonitor(JFrame owner, int in_min_state, int in_max_state) { + super (owner, "Download Flight Data", false); + + GridBagConstraints c; + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = 0; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + flight_label = new JLabel("Flight:"); + pane.add(flight_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + flight_value = new JLabel(""); + pane.add(flight_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 2; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + file_label = new JLabel("File:"); + pane.add(file_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 2; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + file_value = new JLabel(""); + pane.add(file_value, c); + + min_state = in_min_state; + max_state = in_max_state; + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum((max_state - min_state) * 100); + pbar.setValue(0); + pbar.setString("startup"); + pbar.setStringPainted(true); + pbar.setPreferredSize(new Dimension(600, 20)); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 3; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ib = new Insets(4,4,4,4); + c.insets = ib; + pane.add(pbar, c); + + + cancel = new JButton("Cancel"); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 4; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ic = new Insets(4,4,4,4); + c.insets = ic; + pane.add(cancel, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + public void addActionListener (ActionListener l) { + cancel.addActionListener(l); + } + + private void set_value_internal(String state_name, int in_state, int in_block) { + int block = in_block; + int state = in_state; + + if (block > 100) + block = 100; + if (state < min_state) state = min_state; + if (state >= max_state) state = max_state - 1; + state -= min_state; + + int pos = state * 100 + block; + + pbar.setString(state_name); + pbar.setValue(pos); + } + + public void set_value(String in_state_name, int in_state, int in_block) { + final String state_name = in_state_name; + final int state = in_state; + final int block = in_block; + Runnable r = new Runnable() { + public void run() { + try { + set_value_internal(state_name, state, block); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + private void set_serial_internal(int serial) { + serial_value.setText(String.format("%d", serial)); + } + + public void set_serial(int in_serial) { + final int serial = in_serial; + Runnable r = new Runnable() { + public void run() { + try { + set_serial_internal(serial); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + private void set_flight_internal(int flight) { + flight_value.setText(String.format("%d", flight)); + } + + public void set_flight(int in_flight) { + final int flight = in_flight; + Runnable r = new Runnable() { + public void run() { + try { + set_flight_internal(flight); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + private void set_file_internal(String file) { + file_value.setText(String.format("%s", file)); + } + + public void set_file(String in_file) { + final String file = in_file; + Runnable r = new Runnable() { + public void run() { + try { + set_file_internal(file); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + private void done_internal() { + setVisible(false); + dispose(); + } + + public void done() { + Runnable r = new Runnable() { + public void run() { + try { + done_internal(); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + + private void reset_internal() { + set_value_internal("startup",min_state,0); + set_flight_internal(0); + set_file_internal(""); + } + + public void reset() { + Runnable r = new Runnable() { + public void run() { + try { + reset_internal(); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } +} diff --git a/altosui/AltosEepromSelect.java b/altosui/AltosEepromSelect.java new file mode 100644 index 00000000..4ad78896 --- /dev/null +++ b/altosui/AltosEepromSelect.java @@ -0,0 +1,189 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.util.*; +import javax.swing.*; +import javax.swing.border.*; +import java.awt.*; +import java.awt.event.*; +import libaltosJNI.libaltos; +import libaltosJNI.altos_device; +import libaltosJNI.SWIGTYPE_p_altos_file; +import libaltosJNI.SWIGTYPE_p_altos_list; +import org.altusmetrum.AltosLib.*; + +class AltosEepromItem implements ActionListener { + AltosEepromLog log; + JLabel label; + JCheckBox action; + JCheckBox delete; + + public void actionPerformed(ActionEvent e) { + log.selected = action.isSelected(); + } + + public AltosEepromItem(AltosEepromLog in_log) { + log = in_log; + + String text; + if (log.year != 0) + text = String.format("Flight #%02d - %04d-%02d-%02d", + log.flight, log.year, log.month, log.day); + else + text = String.format("Flight #%02d", log.flight); + + label = new JLabel(text); + + action = new JCheckBox("", log.selected); + action.addActionListener(this); + } +} + +public class AltosEepromSelect extends AltosDialog implements ActionListener { + private JList list; + private JFrame frame; + JButton ok; + JButton cancel; + boolean success; + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("ok")) + success = true; + setVisible(false); + } + + public boolean run() { + success = false; + setLocationRelativeTo(frame); + setVisible(true); + return success; + } + + public AltosEepromSelect (JFrame in_frame, + AltosEepromList flights, + String action) { + + super(in_frame, String.format("Flight list for serial %d", flights.config_data.serial), true); + frame = in_frame; + + /* Create the container for the dialog */ + Container contentPane = getContentPane(); + + /* First, we create a pane containing the dialog's header/title */ + JLabel selectLabel = new JLabel(String.format ("Select flights to %s", action), SwingConstants.CENTER); + + JPanel labelPane = new JPanel(); + labelPane.setLayout(new BoxLayout(labelPane, BoxLayout.X_AXIS)); + labelPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + labelPane.add(Box.createHorizontalGlue()); + labelPane.add(selectLabel); + labelPane.add(Box.createHorizontalGlue()); + + /* Add the header to the container. */ + contentPane.add(labelPane, BorderLayout.PAGE_START); + + + /* Now we create the evilness that is a GridBag for the flight details */ + GridBagConstraints c; + Insets i = new Insets(4,4,4,4); + JPanel flightPane = new JPanel(); + flightPane.setLayout(new GridBagLayout()); + flightPane.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + + /* Flight Header */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.weightx = 0.5; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + JLabel flightHeaderLabel = new JLabel("Flight"); + flightPane.add(flightHeaderLabel, c); + + /* Download Header */ + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.weightx = 0.5; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + JLabel downloadHeaderLabel = new JLabel(action); + flightPane.add(downloadHeaderLabel, c); + + /* Add the flights to the GridBag */ + AltosEepromItem item; + int itemNumber = 1; + for (AltosEepromLog flight : flights) { + /* Create a flight object with handlers and + * appropriate UI items + */ + item = new AltosEepromItem(flight); + + /* Add a decriptive label for the flight */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = itemNumber; + c.fill = GridBagConstraints.NONE; + c.weightx = 0.5; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + flightPane.add(item.label, c); + + /* Add action checkbox for the flight */ + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = itemNumber; + c.fill = GridBagConstraints.NONE; + c.weightx = 0.5; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + flightPane.add(item.action, c); + + itemNumber++; + } + + /* Add the GridBag to the container */ + contentPane.add(flightPane, BorderLayout.CENTER); + + /* Create the dialog buttons */ + ok = new JButton("OK"); + ok.addActionListener(this); + ok.setActionCommand("ok"); + + cancel = new JButton("Cancel"); + cancel.addActionListener(this); + cancel.setActionCommand("cancel"); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(cancel); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(ok); + + /* Add the buttons to the container */ + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + /* Pack the window! */ + pack(); + } +} diff --git a/altosui/AltosFlash.java b/altosui/AltosFlash.java new file mode 100644 index 00000000..bd0c8a50 --- /dev/null +++ b/altosui/AltosFlash.java @@ -0,0 +1,380 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosFlash { + File file; + FileInputStream input; + AltosHexfile image; + JFrame frame; + AltosDevice debug_dongle; + AltosDebug debug; + AltosRomconfig rom_config; + ActionListener listener; + boolean aborted; + + static final byte MOV_direct_data = (byte) 0x75; + static final byte MOV_DPTR_data16 = (byte) 0x90; + static final byte MOV_A_data = (byte) 0x74; + static final byte MOVX_atDPTR_A = (byte) 0xf0; + static final byte MOVX_A_atDPTR = (byte) 0xe0; + static final byte INC_DPTR = (byte) 0xa3; + static final byte TRAP = (byte) 0xa5; + + static final byte JB = (byte) 0x20; + + static final byte MOV_A_direct = (byte) 0xe5; + static final byte MOV_direct1_direct2 = (byte) 0x85; + static final byte MOV_direct_A = (byte) 0xf5; + static final byte MOV_R0_data = (byte) (0x78 | 0); + static final byte MOV_R1_data = (byte) (0x78 | 1); + static final byte MOV_R2_data = (byte) (0x78 | 2); + static final byte MOV_R3_data = (byte) (0x78 | 3); + static final byte MOV_R4_data = (byte) (0x78 | 4); + static final byte MOV_R5_data = (byte) (0x78 | 5); + static final byte MOV_R6_data = (byte) (0x78 | 6); + static final byte MOV_R7_data = (byte) (0x78 | 7); + static final byte DJNZ_R0_rel = (byte) (0xd8 | 0); + static final byte DJNZ_R1_rel = (byte) (0xd8 | 1); + static final byte DJNZ_R2_rel = (byte) (0xd8 | 2); + static final byte DJNZ_R3_rel = (byte) (0xd8 | 3); + static final byte DJNZ_R4_rel = (byte) (0xd8 | 4); + static final byte DJNZ_R5_rel = (byte) (0xd8 | 5); + static final byte DJNZ_R6_rel = (byte) (0xd8 | 6); + static final byte DJNZ_R7_rel = (byte) (0xd8 | 7); + + static final byte P1DIR = (byte) 0xFE; + static final byte P1 = (byte) 0x90; + + /* flash controller */ + static final byte FWT = (byte) 0xAB; + static final byte FADDRL = (byte) 0xAC; + static final byte FADDRH = (byte) 0xAD; + static final byte FCTL = (byte) 0xAE; + static final byte FCTL_BUSY = (byte) 0x80; + static final byte FCTL_BUSY_BIT = (byte) 7; + static final byte FCTL_SWBSY = (byte) 0x40; + static final byte FCTL_SWBSY_BIT = (byte) 6; + static final byte FCTL_CONTRD = (byte) 0x10; + static final byte FCTL_WRITE = (byte) 0x02; + static final byte FCTL_ERASE = (byte) 0x01; + static final byte FWDATA = (byte) 0xAF; + + static final byte ACC = (byte) 0xE0; + + /* offsets within the flash_page program */ + static final int FLASH_ADDR_HIGH = 8; + static final int FLASH_ADDR_LOW = 11; + static final int RAM_ADDR_HIGH = 13; + static final int RAM_ADDR_LOW = 14; + static final int FLASH_WORDS_HIGH = 16; + static final int FLASH_WORDS_LOW = 18; + static final int FLASH_TIMING = 21; + + /* sleep mode control */ + static final int SLEEP = (byte) 0xbe; + static final int SLEEP_USB_EN = (byte) 0x80; + static final int SLEEP_XOSC_STB = (byte) 0x40; + static final int SLEEP_HFRC_STB = (byte) 0x20; + static final int SLEEP_RST_MASK = (byte) 0x18; + static final int SLEEP_RST_POWERON = (byte) 0x00; + static final int SLEEP_RST_EXTERNAL = (byte) 0x10; + static final int SLEEP_RST_WATCHDOG = (byte) 0x08; + static final int SLEEP_OSC_PD = (byte) 0x04; + static final int SLEEP_MODE_MASK = (byte) 0x03; + static final int SLEEP_MODE_PM0 = (byte) 0x00; + static final int SLEEP_MODE_PM1 = (byte) 0x01; + static final int SLEEP_MODE_PM2 = (byte) 0x02; + static final int SLEEP_MODE_PM3 = (byte) 0x03; + + /* clock controller */ + static final byte CLKCON = (byte) 0xC6; + static final byte CLKCON_OSC32K = (byte) 0x80; + static final byte CLKCON_OSC = (byte) 0x40; + static final byte CLKCON_TICKSPD = (byte) 0x38; + static final byte CLKCON_CLKSPD = (byte) 0x07; + + static final byte[] flash_page_proto = { + + MOV_direct_data, P1DIR, (byte) 0x02, + MOV_direct_data, P1, (byte) 0xFF, + + MOV_direct_data, FADDRH, 0, /* FLASH_ADDR_HIGH */ + + MOV_direct_data, FADDRL, 0, /* FLASH_ADDR_LOW */ + + MOV_DPTR_data16, 0, 0, /* RAM_ADDR_HIGH, RAM_ADDR_LOW */ + + MOV_R7_data, 0, /* FLASH_WORDS_HIGH */ + + MOV_R6_data, 0, /* FLASH_WORDS_LOW */ + + + MOV_direct_data, FWT, 0x20, /* FLASH_TIMING */ + + MOV_direct_data, FCTL, FCTL_ERASE, +/* eraseWaitLoop: */ + MOV_A_direct, FCTL, + JB, ACC|FCTL_BUSY_BIT, (byte) 0xfb, + + MOV_direct_data, P1, (byte) 0xfd, + + MOV_direct_data, FCTL, FCTL_WRITE, +/* writeLoop: */ + MOV_R5_data, 2, +/* writeWordLoop: */ + MOVX_A_atDPTR, + INC_DPTR, + MOV_direct_A, FWDATA, + DJNZ_R5_rel, (byte) 0xfa, /* writeWordLoop */ +/* writeWaitLoop: */ + MOV_A_direct, FCTL, + JB, ACC|FCTL_SWBSY_BIT, (byte) 0xfb, /* writeWaitLoop */ + DJNZ_R6_rel, (byte) 0xf1, /* writeLoop */ + DJNZ_R7_rel, (byte) 0xef, /* writeLoop */ + + MOV_direct_data, P1DIR, (byte) 0x00, + MOV_direct_data, P1, (byte) 0xFF, + TRAP, + }; + + public byte[] make_flash_page(int flash_addr, int ram_addr, int byte_count) { + int flash_word_addr = flash_addr >> 1; + int flash_word_count = ((byte_count + 1) >> 1); + + byte[] flash_page = new byte[flash_page_proto.length]; + for (int i = 0; i < flash_page.length; i++) + flash_page[i] = flash_page_proto[i]; + + flash_page[FLASH_ADDR_HIGH] = (byte) (flash_word_addr >> 8); + flash_page[FLASH_ADDR_LOW] = (byte) (flash_word_addr); + flash_page[RAM_ADDR_HIGH] = (byte) (ram_addr >> 8); + flash_page[RAM_ADDR_LOW] = (byte) (ram_addr); + + byte flash_words_low = (byte) (flash_word_count); + byte flash_words_high = (byte) (flash_word_count >> 8); + /* the flashing code has a minor 'bug' */ + if (flash_words_low != 0) + flash_words_high++; + + flash_page[FLASH_WORDS_HIGH] = (byte) flash_words_high; + flash_page[FLASH_WORDS_LOW] = (byte) flash_words_low; + return flash_page; + } + + static byte[] set_clkcon_fast = { + MOV_direct_data, CLKCON, 0x00 + }; + + static byte[] get_sleep = { + MOV_A_direct, SLEEP + }; + + public void clock_init() throws IOException, InterruptedException { + if (debug != null) { + debug.debug_instr(set_clkcon_fast); + + byte status; + for (int times = 0; times < 20; times++) { + Thread.sleep(1); + status = debug.debug_instr(get_sleep); + if ((status & SLEEP_XOSC_STB) != 0) + return; + } + throw new IOException("Failed to initialize target clock"); + } + } + + void action(String in_s, int in_percent) { + final String s = in_s; + final int percent = in_percent; + if (listener != null && !aborted) { + Runnable r = new Runnable() { + public void run() { + try { + listener.actionPerformed(new ActionEvent(this, + percent, + s)); + } catch (Exception ex) { + } + } + }; + SwingUtilities.invokeLater(r); + } + } + + void action(int part, int total) { + int percent = 100 * part / total; + action(String.format("%d/%d (%d%%)", + part, total, percent), + percent); + } + + void altos_run(int pc) throws IOException, InterruptedException { + debug.set_pc(pc); + int set_pc = debug.get_pc(); + if (pc != set_pc) + throw new IOException("Failed to set target program counter"); + debug.resume(); + + for (int times = 0; times < 20; times++) { + byte status = debug.read_status(); + if ((status & AltosDebug.STATUS_CPU_HALTED) != 0) + return; + } + + throw new IOException("Failed to execute program on target"); + } + + public void flash() { + try { + if (!check_rom_config()) + throw new IOException("Invalid rom config settings"); + if (image.address + image.data.length > 0x8000) + throw new IOException(String.format("Flash image too long %d", + image.address + + image.data.length)); + if ((image.address & 0x3ff) != 0) + throw new IOException(String.format("Flash image must start on page boundary (is 0x%x)", + image.address)); + int ram_address = 0xf000; + int flash_prog = 0xf400; + + /* + * Store desired config values into image + */ + rom_config.write(image); + /* + * Bring up the clock + */ + clock_init(); + + int remain = image.data.length; + int flash_addr = image.address; + int image_start = 0; + + action("start", 0); + action(0, image.data.length); + while (remain > 0 && !aborted) { + int this_time = remain; + if (this_time > 0x400) + this_time = 0x400; + + if (debug != null) { + /* write the data */ + debug.write_memory(ram_address, image.data, + image_start, this_time); + + /* write the flash program */ + byte[] flash_page = make_flash_page(flash_addr, + ram_address, + this_time); + debug.write_memory(flash_prog, flash_page); + + altos_run(flash_prog); + byte[] check = debug.read_memory(flash_addr, this_time); + for (int i = 0; i < this_time; i++) + if (check[i] != image.data[image_start + i]) + throw new IOException(String.format("Flash write failed at 0x%x (%02x != %02x)", + image.address + image_start + i, + check[i], image.data[image_start + i])); + } else { + Thread.sleep(100); + } + + remain -= this_time; + flash_addr += this_time; + image_start += this_time; + + action(image.data.length - remain, image.data.length); + } + if (!aborted) { + action("done", 100); + if (debug != null) { + debug.set_pc(image.address); + debug.resume(); + } + } + if (debug != null) + debug.close(); + } catch (IOException ie) { + action(ie.getMessage(), -1); + abort(); + } catch (InterruptedException ie) { + abort(); + } + } + + public void close() { + if (debug != null) + debug.close(); + } + + synchronized public void abort() { + aborted = true; + close(); + } + + public void addActionListener(ActionListener l) { + listener = l; + } + + public boolean check_rom_config() { + if (debug == null) + return true; + if (rom_config == null) + rom_config = debug.romconfig(); + return rom_config != null && rom_config.valid(); + } + + public void set_romconfig (AltosRomconfig romconfig) { + rom_config = romconfig; + } + + public AltosRomconfig romconfig() { + if (!check_rom_config()) + return null; + return rom_config; + } + + public AltosFlash(File in_file, AltosDevice in_debug_dongle) + throws IOException, FileNotFoundException, AltosSerialInUseException, InterruptedException { + file = in_file; + debug_dongle = in_debug_dongle; + if (debug_dongle != null) + debug = new AltosDebug(in_debug_dongle); + input = new FileInputStream(file); + image = new AltosHexfile(input); + if (debug != null && !debug.check_connection()) { + debug.close(); + throw new IOException("Debug port not connected"); + } + } +}
\ No newline at end of file diff --git a/altosui/AltosFlashUI.java b/altosui/AltosFlashUI.java new file mode 100644 index 00000000..66991d10 --- /dev/null +++ b/altosui/AltosFlashUI.java @@ -0,0 +1,308 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosFlashUI + extends AltosDialog + implements ActionListener +{ + Container pane; + Box box; + JLabel serial_label; + JLabel serial_value; + JLabel file_label; + JLabel file_value; + JProgressBar pbar; + JButton cancel; + + JFrame frame; + + // Hex file with rom image + File file; + + // Debug connection + AltosDevice debug_dongle; + + // Desired Rom configuration + AltosRomconfig rom_config; + + // Flash controller + AltosFlash flash; + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == cancel) { + if (flash != null) + flash.abort(); + setVisible(false); + dispose(); + } else { + String cmd = e.getActionCommand(); + if (e.getID() == -1) { + JOptionPane.showMessageDialog(frame, + e.getActionCommand(), + file.toString(), + JOptionPane.ERROR_MESSAGE); + setVisible(false); + dispose(); + } else if (cmd.equals("done")) { + setVisible(false); + dispose(); + } else if (cmd.equals("start")) { + setVisible(true); + } else { + pbar.setValue(e.getID()); + pbar.setString(cmd); + } + } + } + + public void build_dialog() { + GridBagConstraints c; + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = 0; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + file_label = new JLabel("File:"); + pane.add(file_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + file_value = new JLabel(file.toString()); + pane.add(file_value, c); + + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum(100); + pbar.setValue(0); + pbar.setString(""); + pbar.setStringPainted(true); + pbar.setPreferredSize(new Dimension(600, 20)); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 2; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ib = new Insets(4,4,4,4); + c.insets = ib; + pane.add(pbar, c); + + cancel = new JButton("Cancel"); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 3; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ic = new Insets(4,4,4,4); + c.insets = ic; + pane.add(cancel, c); + cancel.addActionListener(this); + pack(); + setLocationRelativeTo(frame); + } + + void set_serial(int serial_number) { + serial_value.setText(String.format("%d", serial_number)); + } + + boolean select_source_file() { + JFileChooser hexfile_chooser = new JFileChooser(); + + File firmwaredir = AltosUIPreferences.firmwaredir(); + if (firmwaredir != null) + hexfile_chooser.setCurrentDirectory(firmwaredir); + + hexfile_chooser.setDialogTitle("Select Flash Image"); + hexfile_chooser.setFileFilter(new FileNameExtensionFilter("Flash Image", "ihx")); + int returnVal = hexfile_chooser.showOpenDialog(frame); + + if (returnVal != JFileChooser.APPROVE_OPTION) + return false; + file = hexfile_chooser.getSelectedFile(); + if (file == null) + return false; + AltosUIPreferences.set_firmwaredir(file.getParentFile()); + return true; + } + + boolean select_debug_dongle() { + debug_dongle = AltosDeviceDialog.show(frame, Altos.product_any); + + if (debug_dongle == null) + return false; + return true; + } + + boolean update_rom_config_info(AltosRomconfig existing_config) { + AltosRomconfig new_config; + new_config = AltosRomconfigUI.show(frame, existing_config); + if (new_config == null) + return false; + rom_config = new_config; + set_serial(rom_config.serial_number); + setVisible(true); + return true; + } + + void exception (Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(frame, + ((FileNotFoundException) e).getMessage(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(frame, + String.format("Device \"%s\" already in use", + debug_dongle.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + JOptionPane.showMessageDialog(frame, + e.getMessage(), + file.toString(), + JOptionPane.ERROR_MESSAGE); + } + } + + class flash_task implements Runnable { + AltosFlashUI ui; + Thread t; + AltosFlash flash; + + public void run () { + try { + flash = new AltosFlash(ui.file, ui.debug_dongle); + flash.addActionListener(ui); + + final AltosRomconfig current_config = flash.romconfig(); + + final Semaphore await_rom_config = new Semaphore(0); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + ui.flash = flash; + ui.update_rom_config_info(current_config); + await_rom_config.release(); + } + }); + await_rom_config.acquire(); + + if (ui.rom_config != null) { + flash.set_romconfig(ui.rom_config); + flash.flash(); + } + } catch (InterruptedException ee) { + final Exception e = ee; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + ui.exception(e); + } + }); + } catch (IOException ee) { + final Exception e = ee; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + ui.exception(e); + } + }); + } catch (AltosSerialInUseException ee) { + final Exception e = ee; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + ui.exception(e); + } + }); + } finally { + if (flash != null) + flash.close(); + } + } + + public flash_task(AltosFlashUI in_ui) { + ui = in_ui; + t = new Thread(this); + t.start(); + } + } + + flash_task flasher; + + /* + * Execute the steps for flashing + * a device. Note that this returns immediately; + * this dialog is not modal + */ + void showDialog() { + if (!select_debug_dongle()) + return; + if (!select_source_file()) + return; + build_dialog(); + flash_task f = new flash_task(this); + } + + static void show(JFrame frame) { + AltosFlashUI ui = new AltosFlashUI(frame); + + ui.showDialog(); + } + + public AltosFlashUI(JFrame in_frame) { + super(in_frame, "Program Altusmetrum Device", false); + + frame = in_frame; + } +}
\ No newline at end of file diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java new file mode 100644 index 00000000..826f9522 --- /dev/null +++ b/altosui/AltosFlightDisplay.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import org.altusmetrum.AltosLib.*; + +public interface AltosFlightDisplay { + void reset(); + + void show(AltosState state, int crc_errors); + + void set_font(); +} diff --git a/altosui/AltosFlightInfoTableModel.java b/altosui/AltosFlightInfoTableModel.java new file mode 100644 index 00000000..77969a89 --- /dev/null +++ b/altosui/AltosFlightInfoTableModel.java @@ -0,0 +1,85 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightInfoTableModel extends AbstractTableModel { + final static private String[] columnNames = {"Field", "Value"}; + + int rows; + int cols; + private String[][] data; + + public int getColumnCount() { return cols; } + public int getRowCount() { return rows; } + public String getColumnName(int col) { return columnNames[col & 1]; } + + public Object getValueAt(int row, int col) { + if (row >= rows || col >= cols) + return ""; + return data[row][col]; + } + + int[] current_row; + + public void reset() { + for (int i = 0; i < cols / 2; i++) + current_row[i] = 0; + } + + public void clear() { + reset(); + for (int c = 0; c < cols; c++) + for (int r = 0; r < rows; r++) + data[r][c] = ""; + fireTableDataChanged(); + } + + public void addRow(int col, String name, String value) { + if (current_row[col] < rows) { + data[current_row[col]][col * 2] = name; + data[current_row[col]][col * 2 + 1] = value; + } + current_row[col]++; + } + + public void finish() { + for (int c = 0; c < cols / 2; c++) + while (current_row[c] < rows) + addRow(c, "", ""); + fireTableDataChanged(); + } + + public AltosFlightInfoTableModel (int in_rows, int in_cols) { + rows = in_rows; + cols = in_cols * 2; + data = new String[rows][cols]; + current_row = new int[in_cols]; + } +} diff --git a/altosui/AltosFlightStats.java b/altosui/AltosFlightStats.java new file mode 100644 index 00000000..ab094c80 --- /dev/null +++ b/altosui/AltosFlightStats.java @@ -0,0 +1,162 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightStats { + double max_height; + double max_speed; + double max_acceleration; + double[] state_speed = new double[Altos.ao_flight_invalid + 1]; + double[] state_baro_speed = new double[Altos.ao_flight_invalid + 1]; + double[] state_accel = new double[Altos.ao_flight_invalid + 1]; + int[] state_count = new int[Altos.ao_flight_invalid + 1]; + double[] state_start = new double[Altos.ao_flight_invalid + 1]; + double[] state_end = new double[Altos.ao_flight_invalid + 1]; + int serial; + int flight; + int year, month, day; + int hour, minute, second; + + double landed_time(AltosRecordIterable iterable) { + AltosState state = null; + for (AltosRecord record : iterable) { + state = new AltosState(record, state); + + if (state.state == Altos.ao_flight_landed) + break; + } + + double landed_height = state.height; + + state = null; + + boolean above = true; + + double landed_time = -1000; + + for (AltosRecord record : iterable) { + state = new AltosState(record, state); + + if (state.height > landed_height + 10) { + above = true; + } else { + if (above && state.height < landed_height + 2) { + above = false; + landed_time = state.time; + } + } + } + if (landed_time == -1000) + landed_time = state.time; + return landed_time; + } + + double boost_time(AltosRecordIterable iterable) { + double boost_time = -1000; + + AltosState state = null; + + for (AltosRecord record : iterable) { + state = new AltosState(record, state); + + if (state.acceleration < 1) + boost_time = state.time; + if (state.state >= Altos.ao_flight_boost) + break; + } + if (boost_time == -1000) + boost_time = state.time; + return boost_time; + } + + + public AltosFlightStats(AltosRecordIterable iterable) throws InterruptedException, IOException { + AltosState state = null; + AltosState new_state = null; + double boost_time = boost_time(iterable); + double end_time = 0; + double landed_time = landed_time(iterable); + + year = month = day = -1; + hour = minute = second = -1; + serial = flight = -1; + for (AltosRecord record : iterable) { + if (serial < 0) + serial = record.serial; + if ((record.seen & AltosRecord.seen_flight) != 0 && flight < 0) + flight = record.flight; + new_state = new AltosState(record, state); + end_time = new_state.time; + state = new_state; + if (state.time >= boost_time && state.state < Altos.ao_flight_boost) + state.state = Altos.ao_flight_boost; + if (state.time >= landed_time && state.state < Altos.ao_flight_landed) + state.state = Altos.ao_flight_landed; + if (0 <= state.state && state.state < Altos.ao_flight_invalid) { + if (state.state >= Altos.ao_flight_boost) { + if (state.gps != null && state.gps.locked && + year < 0) { + year = state.gps.year; + month = state.gps.month; + day = state.gps.day; + hour = state.gps.hour; + minute = state.gps.minute; + second = state.gps.second; + } + } + state_accel[state.state] += state.acceleration; + state_speed[state.state] += state.speed; + state_baro_speed[state.state] += state.baro_speed; + state_count[state.state]++; + if (state_start[state.state] == 0.0) + state_start[state.state] = state.time; + if (state_end[state.state] < state.time) + state_end[state.state] = state.time; + max_height = state.max_height; + if (state.max_speed != 0) + max_speed = state.max_speed; + else + max_speed = state.max_baro_speed; + max_acceleration = state.max_acceleration; + } + } + for (int s = Altos.ao_flight_startup; s <= Altos.ao_flight_landed; s++) { + if (state_count[s] > 0) { + state_speed[s] /= state_count[s]; + state_baro_speed[s] /= state_count[s]; + state_accel[s] /= state_count[s]; + } + if (state_start[s] == 0) + state_start[s] = end_time; + if (state_end[s] == 0) + state_end[s] = end_time; + } + } +} diff --git a/altosui/AltosFlightStatsTable.java b/altosui/AltosFlightStatsTable.java new file mode 100644 index 00000000..87ba6aa8 --- /dev/null +++ b/altosui/AltosFlightStatsTable.java @@ -0,0 +1,115 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightStatsTable extends JComponent { + GridBagLayout layout; + + class FlightStat { + JLabel label; + JTextField value; + + public FlightStat(GridBagLayout layout, int y, String label_text, String ... values) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.weighty = 1; + + label = new JLabel(label_text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + for (int j = 0; j < values.length; j++) { + value = new JTextField(values[j]); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = j+1; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + } + } + + } + + public AltosFlightStatsTable(AltosFlightStats stats) { + layout = new GridBagLayout(); + + setLayout(layout); + int y = 0; + new FlightStat(layout, y++, "Serial", String.format("%d", stats.serial)); + new FlightStat(layout, y++, "Flight", String.format("%d", stats.flight)); + if (stats.year > 0) + new FlightStat(layout, y++, "Date", + String.format("%04d-%02d-%02d", stats.year, stats.month, stats.day)); + if (stats.hour > 0) + new FlightStat(layout, y++, "Time", + String.format("%02d:%02d:%02d UTC", stats.hour, stats.minute, stats.second)); + new FlightStat(layout, y++, "Maximum height", + String.format("%5.0f m", stats.max_height), + String.format("%5.0f ft", AltosConvert.meters_to_feet(stats.max_height))); + new FlightStat(layout, y++, "Maximum speed", + String.format("%5.0f m/s", stats.max_speed), + String.format("%5.0f mph", AltosConvert.meters_to_mph(stats.max_speed)), + String.format("Mach %4.1f", AltosConvert.meters_to_mach(stats.max_speed))); + if (stats.max_acceleration != AltosRecord.MISSING) { + new FlightStat(layout, y++, "Maximum boost acceleration", + String.format("%5.0f m/s²", stats.max_acceleration), + String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.max_acceleration)), + String.format("%5.0f G", AltosConvert.meters_to_g(stats.max_acceleration))); + new FlightStat(layout, y++, "Average boost acceleration", + String.format("%5.0f m/s²", stats.state_accel[Altos.ao_flight_boost]), + String.format("%5.0f ft/s²", AltosConvert.meters_to_feet(stats.state_accel[Altos.ao_flight_boost])), + String.format("%5.0f G", AltosConvert.meters_to_g(stats.state_accel[Altos.ao_flight_boost]))); + } + new FlightStat(layout, y++, "Drogue descent rate", + String.format("%5.0f m/s", stats.state_baro_speed[Altos.ao_flight_drogue]), + String.format("%5.0f ft/s", AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_drogue]))); + new FlightStat(layout, y++, "Main descent rate", + String.format("%5.0f m/s", stats.state_baro_speed[Altos.ao_flight_main]), + String.format("%5.0f ft/s", AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_main]))); + for (int s = Altos.ao_flight_boost; s <= Altos.ao_flight_main; s++) { + new FlightStat(layout, y++, String.format("%s time", AltosLib.state_name_capital(s)), + String.format("%6.0f s", stats.state_end[s] - stats.state_start[s])); + } + new FlightStat(layout, y++, "Flight Time", + String.format("%6.0f s", stats.state_end[Altos.ao_flight_main] - + stats.state_start[Altos.ao_flight_boost])); + + } + +}
\ No newline at end of file diff --git a/altosui/AltosFlightStatus.java b/altosui/AltosFlightStatus.java new file mode 100644 index 00000000..6a351004 --- /dev/null +++ b/altosui/AltosFlightStatus.java @@ -0,0 +1,185 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightStatus extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public class FlightValue { + JLabel label; + JTextField value; + + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + + void set_font() { + label.setFont(Altos.status_font); + value.setFont(Altos.status_font); + } + + public FlightValue (GridBagLayout layout, int x, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(5, 5, 5, 5); + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.status_font); + label.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(""); + value.setFont(Altos.status_font); + value.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 1; + layout.setConstraints(value, c); + add(value); + } + } + + class Call extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(state.data.callsign); + } + public Call (GridBagLayout layout, int x) { + super (layout, x, "Callsign"); + } + } + + Call call; + + class Serial extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.serial)); + } + public Serial (GridBagLayout layout, int x) { + super (layout, x, "Serial"); + } + } + + Serial serial; + + class Flight extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.flight)); + } + public Flight (GridBagLayout layout, int x) { + super (layout, x, "Flight"); + } + } + + Flight flight; + + class FlightState extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(state.data.state()); + } + public FlightState (GridBagLayout layout, int x) { + super (layout, x, "State"); + } + } + + FlightState flight_state; + + class RSSI extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.rssi)); + } + public RSSI (GridBagLayout layout, int x) { + super (layout, x, "RSSI"); + } + } + + RSSI rssi; + + class LastPacket extends FlightValue { + void show(AltosState state, int crc_errors) { + long secs = (System.currentTimeMillis() - state.report_time + 500) / 1000; + value.setText(String.format("%d", secs)); + } + public LastPacket(GridBagLayout layout, int x) { + super (layout, x, "Age"); + } + } + + LastPacket last_packet; + + public void reset () { + call.reset(); + serial.reset(); + flight.reset(); + flight_state.reset(); + rssi.reset(); + last_packet.reset(); + } + + public void set_font () { + call.set_font(); + serial.set_font(); + flight.set_font(); + flight_state.set_font(); + rssi.set_font(); + last_packet.set_font(); + } + + public void show (AltosState state, int crc_errors) { + call.show(state, crc_errors); + serial.show(state, crc_errors); + flight.show(state, crc_errors); + flight_state.show(state, crc_errors); + rssi.show(state, crc_errors); + last_packet.show(state, crc_errors); + } + + public int height() { + Dimension d = layout.preferredLayoutSize(this); + return d.height; + } + + public AltosFlightStatus() { + layout = new GridBagLayout(); + + setLayout(layout); + + call = new Call(layout, 0); + serial = new Serial(layout, 1); + flight = new Flight(layout, 2); + flight_state = new FlightState(layout, 3); + rssi = new RSSI(layout, 4); + last_packet = new LastPacket(layout, 5); + } +} diff --git a/altosui/AltosFlightStatusTableModel.java b/altosui/AltosFlightStatusTableModel.java new file mode 100644 index 00000000..c2cf8cd1 --- /dev/null +++ b/altosui/AltosFlightStatusTableModel.java @@ -0,0 +1,67 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightStatusTableModel extends AbstractTableModel { + private String[] columnNames = { + String.format("Height (%s)", AltosConvert.show_distance_units()), + "State", + "RSSI (dBm)", + String.format("Speed (%s)", AltosConvert.show_speed_unit()) + }; + private Object[] data = { 0, "idle", 0, 0 }; + + public int getColumnCount() { return columnNames.length; } + public int getRowCount() { return 2; } + public Object getValueAt(int row, int col) { + if (row == 0) + return columnNames[col]; + return data[col]; + } + + public void setValueAt(Object value, int col) { + data[col] = value; + fireTableCellUpdated(1, col); + } + + public void setValueAt(Object value, int row, int col) { + setValueAt(value, col); + } + + public void set(AltosState state) { + setValueAt(String.format("%1.0f", AltosConvert.distance(state.height), 0); + setValueAt(state.data.state(), 1); + setValueAt(state.data.rssi, 2); + double speed = state.baro_speed; + if (state.ascent) + speed = state.speed; + setValueAt(String.format("%1.0f", AltosConvert.speed(speed)), 3); + } +} diff --git a/altosui/AltosFlightStatusUpdate.java b/altosui/AltosFlightStatusUpdate.java new file mode 100644 index 00000000..d70fc7f8 --- /dev/null +++ b/altosui/AltosFlightStatusUpdate.java @@ -0,0 +1,46 @@ +/* + * Copyright © 2012 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightStatusUpdate implements ActionListener { + + public AltosState saved_state; + AltosFlightStatus flightStatus; + + public void actionPerformed (ActionEvent e) { + if (saved_state != null) + flightStatus.show(saved_state, 0); + } + + public AltosFlightStatusUpdate (AltosFlightStatus in_flightStatus) { + flightStatus = in_flightStatus; + } +} + diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java new file mode 100644 index 00000000..ddc54cbd --- /dev/null +++ b/altosui/AltosFlightUI.java @@ -0,0 +1,331 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosFlightUI extends AltosFrame implements AltosFlightDisplay, AltosFontListener { + AltosVoice voice; + AltosFlightReader reader; + AltosDisplayThread thread; + + JTabbedPane pane; + + AltosPad pad; + AltosAscent ascent; + AltosDescent descent; + AltosLanded landed; + AltosCompanionInfo companion; + AltosSiteMap sitemap; + boolean has_map; + boolean has_companion; + + private AltosFlightStatus flightStatus; + private AltosInfoTable flightInfo; + + boolean exit_on_close = false; + + JComponent cur_tab = null; + JComponent which_tab(AltosState state) { + if (state.state < Altos.ao_flight_boost) + return pad; + if (state.state <= Altos.ao_flight_coast) + return ascent; + if (state.state <= Altos.ao_flight_main) + return descent; + return landed; + } + + void stop_display() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + } + + void disconnect() { + stop_display(); + } + + public void reset() { + pad.reset(); + ascent.reset(); + descent.reset(); + landed.reset(); + flightInfo.clear(); + sitemap.reset(); + } + + public void set_font() { + pad.set_font(); + ascent.set_font(); + descent.set_font(); + landed.set_font(); + flightStatus.set_font(); + flightInfo.set_font(); + sitemap.set_font(); + companion.set_font(); + } + + public void font_size_changed(int font_size) { + set_font(); + } + + + AltosFlightStatusUpdate status_update; + + public void show(AltosState state, int crc_errors) { + status_update.saved_state = state; + JComponent tab = which_tab(state); + try { + pad.show(state, crc_errors); + ascent.show(state, crc_errors); + descent.show(state, crc_errors); + landed.show(state, crc_errors); + if (tab != cur_tab) { + if (cur_tab == pane.getSelectedComponent()) { + pane.setSelectedComponent(tab); + } + cur_tab = tab; + } + flightStatus.show(state, crc_errors); + flightInfo.show(state, crc_errors); + + if (state.data.companion != null) { + if (!has_companion) { + pane.add("Companion", companion); + has_companion= true; + } + companion.show(state, crc_errors); + } else { + if (has_companion) { + pane.remove(companion); + has_companion = false; + } + } + if (state.gps != null && state.gps.connected) { + if (!has_map) { + pane.add("Site Map", sitemap); + has_map = true; + } + sitemap.show(state, crc_errors); + } else { + if (has_map) { + pane.remove(sitemap); + has_map = false; + } + } + } catch (Exception e) { + System.out.print("Show exception" + e); + } + } + + public void set_exit_on_close() { + exit_on_close = true; + } + + Container bag; + AltosFreqList frequencies; + JComboBox telemetries; + JLabel telemetry; + + ActionListener show_timer; + + public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) { + AltosUIPreferences.set_component(this); + + voice = in_voice; + reader = in_reader; + + bag = getContentPane(); + bag.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + + java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg"); + if (imgURL != null) + setIconImage(new ImageIcon(imgURL).getImage()); + + setTitle(String.format("AltOS %s", reader.name)); + + /* Stick channel selector at top of table for telemetry monitoring */ + if (serial >= 0) { + // Channel menu + frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial)); + frequencies.set_product("Monitor"); + frequencies.set_serial(serial); + frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + double frequency = frequencies.frequency(); + try { + reader.set_frequency(frequency); + } catch (TimeoutException te) { + } catch (InterruptedException ie) { + } + reader.save_frequency(); + } + }); + c.gridx = 0; + c.gridy = 0; + c.weightx = 0; + c.weighty = 0; + c.insets = new Insets(3, 3, 3, 3); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + bag.add (frequencies, c); + + // Telemetry format menu + if (reader.supports_telemetry(Altos.ao_telemetry_standard)) { + telemetries = new JComboBox(); + for (int i = 1; i <= Altos.ao_telemetry_max; i++) + telemetries.addItem(Altos.telemetry_name(i)); + int telemetry = AltosPreferences.telemetry(serial); + if (telemetry <= Altos.ao_telemetry_off || + telemetry > Altos.ao_telemetry_max) + telemetry = Altos.ao_telemetry_standard; + telemetries.setSelectedIndex(telemetry - 1); + telemetries.setMaximumRowCount(Altos.ao_telemetry_max); + telemetries.setPreferredSize(null); + telemetries.revalidate(); + telemetries.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int telemetry = telemetries.getSelectedIndex() + 1; + reader.set_telemetry(telemetry); + reader.save_telemetry(); + } + }); + c.gridx = 1; + c.gridy = 0; + c.weightx = 0; + c.weighty = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + bag.add (telemetries, c); + c.insets = new Insets(0, 0, 0, 0); + } else { + String version; + + if (reader.supports_telemetry(Altos.ao_telemetry_0_9)) + version = "Telemetry: 0.9"; + else if (reader.supports_telemetry(Altos.ao_telemetry_0_8)) + version = "Telemetry: 0.8"; + else + version = "Telemetry: None"; + + telemetry = new JLabel(version); + c.gridx = 1; + c.gridy = 0; + c.weightx = 0; + c.weighty = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + bag.add (telemetry, c); + c.insets = new Insets(0, 0, 0, 0); + } + } + + /* Flight status is always visible */ + flightStatus = new AltosFlightStatus(); + c.gridx = 0; + c.gridy = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridwidth = 2; + bag.add(flightStatus, c); + c.gridwidth = 1; + + /* The rest of the window uses a tabbed pane to + * show one of the alternate data views + */ + pane = new JTabbedPane(); + + pad = new AltosPad(); + pane.add("Launch Pad", pad); + + ascent = new AltosAscent(); + pane.add("Ascent", ascent); + + descent = new AltosDescent(); + pane.add("Descent", descent); + + landed = new AltosLanded(reader); + pane.add("Landed", landed); + + flightInfo = new AltosInfoTable(); + pane.add("Table", new JScrollPane(flightInfo)); + + companion = new AltosCompanionInfo(); + has_companion = false; + + sitemap = new AltosSiteMap(); + has_map = false; + + /* Make the tabbed pane use the rest of the window space */ + c.gridx = 0; + c.gridy = 2; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + c.gridwidth = 2; + bag.add(pane, c); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + AltosUIPreferences.register_font_listener(this); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + disconnect(); + setVisible(false); + dispose(); + AltosUIPreferences.unregister_font_listener(AltosFlightUI.this); + if (exit_on_close) + System.exit(0); + } + }); + + pack(); + setVisible(true); + + thread = new AltosDisplayThread(this, voice, this, reader); + + status_update = new AltosFlightStatusUpdate(flightStatus); + + new javax.swing.Timer(100, status_update).start(); + + thread.start(); + } + + public AltosFlightUI (AltosVoice in_voice, AltosFlightReader in_reader) { + this(in_voice, in_reader, -1); + } +} diff --git a/altosui/AltosFontListener.java b/altosui/AltosFontListener.java new file mode 100644 index 00000000..0dda0f29 --- /dev/null +++ b/altosui/AltosFontListener.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public interface AltosFontListener { + void font_size_changed(int font_size); +} diff --git a/altosui/AltosFrame.java b/altosui/AltosFrame.java new file mode 100644 index 00000000..70598634 --- /dev/null +++ b/altosui/AltosFrame.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +class AltosFrameListener extends WindowAdapter { + public void windowClosing (WindowEvent e) { + AltosUIPreferences.unregister_ui_listener((AltosFrame) e.getWindow()); + } +} + +public class AltosFrame extends JFrame implements AltosUIListener { + + public void ui_changed(String look_and_feel) { + SwingUtilities.updateComponentTreeUI(this); + this.pack(); + } + + public AltosFrame() { + AltosUIPreferences.register_ui_listener(this); + addWindowListener(new AltosFrameListener()); + } + + public AltosFrame(String name) { + super(name); + AltosUIPreferences.register_ui_listener(this); + addWindowListener(new AltosFrameListener()); + } +} diff --git a/altosui/AltosFreqList.java b/altosui/AltosFreqList.java new file mode 100644 index 00000000..1bbc97c6 --- /dev/null +++ b/altosui/AltosFreqList.java @@ -0,0 +1,88 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosFreqList extends JComboBox { + + String product; + int serial; + int calibrate; + + public void set_frequency(double new_frequency) { + int i; + for (i = 0; i < getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) getItemAt(i); + + if (f.close(new_frequency)) { + setSelectedIndex(i); + return; + } + } + for (i = 0; i < getItemCount(); i++) { + AltosFrequency f = (AltosFrequency) getItemAt(i); + + if (new_frequency < f.frequency) + break; + } + String description = String.format("%s serial %d", product, serial); + AltosFrequency frequency = new AltosFrequency(new_frequency, description); + AltosUIPreferences.add_common_frequency(frequency); + insertItemAt(frequency, i); + setMaximumRowCount(getItemCount()); + } + + public void set_product(String new_product) { + product = new_product; + } + + public void set_serial(int new_serial) { + serial = new_serial; + } + + public double frequency() { + AltosFrequency f = (AltosFrequency) getSelectedItem(); + if (f != null) + return f.frequency; + return 434.550; + } + + public AltosFreqList () { + super(AltosUIPreferences.common_frequencies()); + setMaximumRowCount(getItemCount()); + setEditable(false); + product = "Unknown"; + serial = 0; + } + + public AltosFreqList(double in_frequency) { + this(); + set_frequency(in_frequency); + } +} diff --git a/altosui/AltosGraph.java b/altosui/AltosGraph.java new file mode 100644 index 00000000..54d2bb0b --- /dev/null +++ b/altosui/AltosGraph.java @@ -0,0 +1,27 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.*; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.ChartUtilities; +import org.altusmetrum.AltosLib.*; + +abstract class AltosGraph { + public String filename; + public abstract void addData(AltosDataPoint d); + public abstract JFreeChart createChart(); + public String title; + public void toPNG() throws java.io.IOException { toPNG(300, 500); } + public void toPNG(int width, int height) + throws java.io.IOException + { + File pngout = new File(filename); + JFreeChart chart = createChart(); + ChartUtilities.saveChartAsPNG(pngout, chart, width, height); + System.out.println("Created " + filename); + } +} diff --git a/altosui/AltosGraphTime.java b/altosui/AltosGraphTime.java new file mode 100644 index 00000000..0955f6e6 --- /dev/null +++ b/altosui/AltosGraphTime.java @@ -0,0 +1,236 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; +import java.text.*; +import java.awt.Color; +import java.util.ArrayList; +import java.util.HashMap; +import org.altusmetrum.AltosLib.*; + +import org.jfree.chart.ChartUtilities; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.renderer.xy.StandardXYItemRenderer; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import org.jfree.ui.RectangleAnchor; +import org.jfree.ui.TextAnchor; + +class AltosGraphTime extends AltosGraph { + static interface Element { + void attachGraph(AltosGraphTime g); + void gotTimeData(double time, AltosDataPoint d); + void addToPlot(AltosGraphTime g, XYPlot plot); + } + + static class TimeAxis implements Element { + private int axis; + private Color color; + private String label; + private AxisLocation locn; + private double min_y = Double.NaN; + + public TimeAxis(int axis, String label, Color color, AxisLocation locn) + { + this.axis = axis; + this.color = color; + this.label = label; + this.locn = locn; + } + + public void setLowerBound(double min_y) { + this.min_y = min_y; + } + + public void attachGraph(AltosGraphTime g) { return; } + public void gotTimeData(double time, AltosDataPoint d) { return; } + + public void addToPlot(AltosGraphTime g, XYPlot plot) { + NumberAxis numAxis = new NumberAxis(label); + if (!Double.isNaN(min_y)) + numAxis.setLowerBound(min_y); + plot.setRangeAxis(axis, numAxis); + plot.setRangeAxisLocation(axis, locn); + numAxis.setLabelPaint(color); + numAxis.setTickLabelPaint(color); + numAxis.setAutoRangeIncludesZero(false); + } + } + + abstract static class TimeSeries implements Element { + protected XYSeries series; + private String axisName; + private Color color; + + public TimeSeries(String axisName, String label, Color color) { + this.series = new XYSeries(label); + this.axisName = axisName; + this.color = color; + } + + public void attachGraph(AltosGraphTime g) { + g.setAxis(this, axisName, color); + } + abstract public void gotTimeData(double time, AltosDataPoint d); + + public void addToPlot(AltosGraphTime g, XYPlot plot) { + XYSeriesCollection dataset = new XYSeriesCollection(); + dataset.addSeries(this.series); + + XYItemRenderer renderer = new StandardXYItemRenderer(); + renderer.setSeriesPaint(0, color); + + int dataNum = g.getDataNum(this); + int axisNum = g.getAxisNum(this); + + plot.setDataset(dataNum, dataset); + plot.mapDatasetToRangeAxis(dataNum, axisNum); + plot.setRenderer(dataNum, renderer); + } + } + + static class StateMarker implements Element { + private LinkedList<Double> times = new LinkedList<Double>(); + private String name; + private int state; + private int prev_state = Altos.ao_flight_startup; + + StateMarker(int state, String name) { + this.state = state; + this.name = name; + } + + public void attachGraph(AltosGraphTime g) { return; } + public void gotTimeData(double time, AltosDataPoint d) { + if (prev_state != state && d.state() == state) + times.add(time); + prev_state = d.state(); + } + + public void addToPlot(AltosGraphTime g, XYPlot plot) { + for (double time : times) { + ValueMarker m = new ValueMarker(time); + m.setLabel(name); + m.setLabelAnchor(RectangleAnchor.TOP_RIGHT); + m.setLabelTextAnchor(TextAnchor.TOP_LEFT); + plot.addDomainMarker(m); + } + } + } + + private String callsign = null; + private Integer serial = null; + private Integer flight = null; + + private ArrayList<Element> elements; + private HashMap<String,Integer> axes; + private HashMap<Element,Integer> datasets; + private ArrayList<Integer> datasetAxis; + + public AltosGraphTime(String title) { + this.filename = title.toLowerCase().replaceAll("[^a-z0-9]","_")+".png"; + this.title = title; + this.elements = new ArrayList<Element>(); + this.axes = new HashMap<String,Integer>(); + this.datasets = new HashMap<Element,Integer>(); + this.datasetAxis = new ArrayList<Integer>(); + } + + public AltosGraphTime addElement(Element e) { + e.attachGraph(this); + elements.add(e); + return this; + } + + public void setAxis(Element ds, String axisName, Color color) { + Integer axisNum = axes.get(axisName); + int dsNum = datasetAxis.size(); + if (axisNum == null) { + axisNum = newAxis(axisName, color); + } + datasets.put(ds, dsNum); + datasetAxis.add(axisNum); + } + + public int getAxisNum(Element ds) { + return datasetAxis.get( datasets.get(ds) ).intValue(); + } + public int getDataNum(Element ds) { + return datasets.get(ds).intValue(); + } + + private Integer newAxis(String name, Color color) { + int cnt = axes.size(); + AxisLocation locn = AxisLocation.BOTTOM_OR_LEFT; + if (cnt > 0) { + locn = AxisLocation.TOP_OR_RIGHT; + } + Integer res = new Integer(cnt); + axes.put(name, res); + this.addElement(new TimeAxis(cnt, name, color, locn)); + return res; + } + + public void addData(AltosDataPoint d) { + double time = d.time(); + for (Element e : elements) { + e.gotTimeData(time, d); + } + if (callsign == null) callsign = d.callsign(); + if (serial == null) serial = new Integer(d.serial()); + if (flight == null) flight = new Integer(d.flight()); + } + + public JFreeChart createChart() { + NumberAxis xAxis = new NumberAxis("Time (s)"); + xAxis.setAutoRangeIncludesZero(false); + XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false); + XYPlot plot = new XYPlot(); + plot.setDomainAxis(xAxis); + plot.setRenderer(renderer); + plot.setOrientation(PlotOrientation.VERTICAL); + + if (serial != null && flight != null) { + title = serial + "/" + flight + ": " + title; + } + if (callsign != null) { + title = callsign + " - " + title; + } + + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator()); + JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, + plot, true); + ChartUtilities.applyCurrentTheme(chart); + + plot.setDomainPannable(true); + plot.setRangePannable(true); + + for (Element e : elements) { + e.addToPlot(this, plot); + } + + return chart; + } + + public void toPNG() throws java.io.IOException { + if (axes.size() > 1) { + toPNG(800, 500); + } else { + toPNG(300, 500); + } + } +} diff --git a/altosui/AltosGraphUI.java b/altosui/AltosGraphUI.java new file mode 100644 index 00000000..edde1307 --- /dev/null +++ b/altosui/AltosGraphUI.java @@ -0,0 +1,313 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.*; +import java.util.ArrayList; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import org.altusmetrum.AltosLib.*; + +import org.jfree.chart.ChartPanel; +import org.jfree.chart.ChartUtilities; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.ui.ApplicationFrame; +import org.jfree.ui.RefineryUtilities; + +public class AltosGraphUI extends AltosFrame +{ + JTabbedPane pane; + + static final private Color red = new Color(194,31,31); + static final private Color green = new Color(31,194,31); + static final private Color blue = new Color(31,31,194); + static final private Color black = new Color(31,31,31); + static final private Color yellow = new Color(194,194,31); + static final private Color cyan = new Color(31,194,194); + static final private Color magenta = new Color(194,31,194); + + static private class OverallGraphs { + AltosGraphTime.Element height = + new AltosGraphTime.TimeSeries(String.format("Height (%s)", AltosConvert.height.show_units()), "Height (AGL)", red) { + public void gotTimeData(double time, AltosDataPoint d) { + double height = d.height(); + if (height != AltosRecord.MISSING) + series.add(time, AltosConvert.height.value(height)); + } + }; + + AltosGraphTime.Element speed = + new AltosGraphTime.TimeSeries(String.format("Speed (%s)", AltosConvert.speed.show_units()), "Vertical Speed", green) { + public void gotTimeData(double time, AltosDataPoint d) { + double speed; + if (d.state() < Altos.ao_flight_drogue && d.has_accel()) { + speed = d.accel_speed(); + } else { + speed = d.baro_speed(); + } + if (speed != AltosRecord.MISSING) + series.add(time, AltosConvert.speed.value(speed)); + } + }; + + AltosGraphTime.Element acceleration = + new AltosGraphTime.TimeSeries(String.format("Acceleration (%s)", + AltosConvert.accel.show_units()), + "Axial Acceleration", blue) + { + public void gotTimeData(double time, AltosDataPoint d) { + double acceleration = d.acceleration(); + if (acceleration != AltosRecord.MISSING) + series.add(time, AltosConvert.accel.value(acceleration)); + } + }; + + AltosGraphTime.Element temperature = + new AltosGraphTime.TimeSeries("Temperature (\u00B0C)", + "Board temperature", red) + { + public void gotTimeData(double time, AltosDataPoint d) { + double temp = d.temperature(); + if (temp != AltosRecord.MISSING) + series.add(time, d.temperature()); + } + }; + + AltosGraphTime.Element drogue_voltage = + new AltosGraphTime.TimeSeries("Voltage (V)", "Drogue Continuity", yellow) + { + public void gotTimeData(double time, AltosDataPoint d) { + double v = d.drogue_voltage(); + if (v != AltosRecord.MISSING) + series.add(time, v); + } + }; + + AltosGraphTime.Element main_voltage = + new AltosGraphTime.TimeSeries("Voltage (V)", "Main Continuity", magenta) + { + public void gotTimeData(double time, AltosDataPoint d) { + double v = d.main_voltage(); + if (v != AltosRecord.MISSING) + series.add(time, v); + } + }; + + AltosGraphTime.Element e_pad = new AltosGraphTime.StateMarker(Altos.ao_flight_pad, "Pad"); + AltosGraphTime.Element e_boost = new AltosGraphTime.StateMarker(Altos.ao_flight_boost, "Boost"); + AltosGraphTime.Element e_fast = new AltosGraphTime.StateMarker(Altos.ao_flight_fast, "Fast"); + AltosGraphTime.Element e_coast = new AltosGraphTime.StateMarker(Altos.ao_flight_coast, "Coast"); + AltosGraphTime.Element e_drogue = new AltosGraphTime.StateMarker(Altos.ao_flight_drogue, "Drogue"); + AltosGraphTime.Element e_main = new AltosGraphTime.StateMarker(Altos.ao_flight_main, "Main"); + AltosGraphTime.Element e_landed = new AltosGraphTime.StateMarker(Altos.ao_flight_landed, "Landed"); + + protected AltosGraphTime myAltosGraphTime(String suffix) { + return (new AltosGraphTime("Overall " + suffix)) + .addElement(e_boost) + .addElement(e_fast) + .addElement(e_coast) + .addElement(e_drogue) + .addElement(e_main) + .addElement(e_landed); + } + + public ArrayList<AltosGraph> graphs() { + ArrayList<AltosGraph> graphs = new ArrayList<AltosGraph>(); + + graphs.add( myAltosGraphTime("Summary") + .addElement(height) + .addElement(speed) + .addElement(acceleration) ); + + graphs.add( myAltosGraphTime("Summary") + .addElement(height) + .addElement(speed)); + + graphs.add( myAltosGraphTime("Altitude") + .addElement(height) ); + + graphs.add( myAltosGraphTime("Speed") + .addElement(speed) ); + + graphs.add( myAltosGraphTime("Acceleration") + .addElement(acceleration) ); + + graphs.add( myAltosGraphTime("Temperature") + .addElement(temperature) ); + + graphs.add( myAltosGraphTime("Continuity") + .addElement(drogue_voltage) + .addElement(main_voltage) ); + + return graphs; + } + } + + static private class AscentGraphs extends OverallGraphs { + protected AltosGraphTime myAltosGraphTime(String suffix) { + return (new AltosGraphTime("Ascent " + suffix) { + public void addData(AltosDataPoint d) { + int state = d.state(); + if (Altos.ao_flight_boost <= state && state <= Altos.ao_flight_coast) { + super.addData(d); + } + } + }).addElement(e_boost) + .addElement(e_fast) + .addElement(e_coast); + } + } + + static private class DescentGraphs extends OverallGraphs { + protected AltosGraphTime myAltosGraphTime(String suffix) { + return (new AltosGraphTime("Descent " + suffix) { + public void addData(AltosDataPoint d) { + int state = d.state(); + if (Altos.ao_flight_drogue <= state && state <= Altos.ao_flight_main) { + super.addData(d); + } + } + }).addElement(e_drogue) + .addElement(e_main); + // ((XYGraph)graph[8]).ymin = new Double(-50); + } + } + + public AltosGraphUI(AltosRecordIterable records, String name) throws InterruptedException, IOException { + super(String.format("Altos Graph %s", name)); + + AltosDataPointReader reader = new AltosDataPointReader (records); + if (reader == null) + return; + + if (reader.has_accel) + init(reader, records, 0); + else + init(reader, records, 1); + } + +// public AltosGraphUI(AltosDataPointReader data, int which) + // { +// super("Altos Graph"); +// init(data, which); +// } + + private void init(AltosDataPointReader data, AltosRecordIterable records, int which) throws InterruptedException, IOException { + pane = new JTabbedPane(); + + AltosGraph graph = createGraph(data, which); + + JFreeChart chart = graph.createChart(); + ChartPanel chartPanel = new ChartPanel(chart); + chartPanel.setMouseWheelEnabled(true); + chartPanel.setPreferredSize(new java.awt.Dimension(800, 500)); + pane.add(graph.title, chartPanel); + + AltosFlightStatsTable stats = new AltosFlightStatsTable(new AltosFlightStats(records)); + pane.add("Flight Statistics", stats); + + setContentPane (pane); + + pack(); + + RefineryUtilities.centerFrameOnScreen(this); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setVisible(true); + } + + private static AltosGraph createGraph(Iterable<AltosDataPoint> data, + int which) + { + return createGraphsWhich(data, which).get(0); + } + + private static ArrayList<AltosGraph> createGraphs( + Iterable<AltosDataPoint> data) + { + return createGraphsWhich(data, -1); + } + + private static ArrayList<AltosGraph> createGraphsWhich( + Iterable<AltosDataPoint> data, int which) + { + ArrayList<AltosGraph> graph = new ArrayList<AltosGraph>(); + graph.addAll((new OverallGraphs()).graphs()); +// graph.addAll((new AscentGraphs()).graphs()); +// graph.addAll((new DescentGraphs()).graphs()); + + if (which > 0) { + if (which >= graph.size()) { + which = 0; + } + AltosGraph g = graph.get(which); + graph = new ArrayList<AltosGraph>(); + graph.add(g); + } + + for (AltosDataPoint dp : data) { + for (AltosGraph g : graph) { + g.addData(dp); + } + } + + return graph; + } +} + +/* gnuplot bits... + * +300x400 + +-------------------------------------------------------- +TOO HARD! :) + +"ascent-gps-accuracy.png" "Vertical error margin to apogee - GPS v Baro (m)" + 5:($7 < 6 ? $24-$11 : 1/0) +"descent-gps-accuracy.png" "Vertical error margin during descent - GPS v Baro (m)" + 5:($7 < 6 ? 1/0 : $24-$11) + +set output "overall-gps-accuracy.png" +set ylabel "distance above sea level (m)" +plot "telemetry.csv" using 5:11 with lines ti "baro altitude" axis x1y1, \ + "telemetry.csv" using 5:24 with lines ti "gps altitude" axis x1y1 + +set term png tiny size 700,700 enhanced +set xlabel "m" +set ylabel "m" +set polar +set grid polar +set rrange[*:*] +set angles degrees + +set output "overall-gps-path.png" +#:30 with yerrorlines +plot "telemetry.csv" using (90-$33):($7 == 2 ? $31 : 1/0) with lines ti "pad", \ + "telemetry.csv" using (90-$33):($7 == 3 ? $31 : 1/0) with lines ti "boost", \ + "telemetry.csv" using (90-$33):($7 == 4 ? $31 : 1/0) with lines ti "fast", \ + "telemetry.csv" using (90-$33):($7 == 5 ? $31 : 1/0) with lines ti "coast", \ + "telemetry.csv" using (90-$33):($7 == 6 ? $31 : 1/0) with lines ti "drogue", \ + "telemetry.csv" using (90-$33):($7 == 7 ? $31 : 1/0) with lines ti "main", \ + "telemetry.csv" using (90-$33):($7 == 8 ? $31 : 1/0) with lines ti "landed" + +set output "ascent-gps-path.png" +plot "telemetry.csv" using (90-$33):($7 == 2 ? $31 : 1/0):30 with lines ti "pad", \ + "telemetry.csv" using (90-$33):($7 == 3 ? $31 : 1/0):20 with lines ti "boost", \ + "telemetry.csv" using (90-$33):($7 == 4 ? $31 : 1/0):10 with lines ti "fast", \ + "telemetry.csv" using (90-$33):($7 == 5 ? $31 : 1/0):5 with lines ti "coast" + +set output "descent-gps-path.png" +plot "telemetry.csv" using (90-$33):($7 == 6 ? $31 : 1/0) with lines ti "drogue", \ + "telemetry.csv" using (90-$33):($7 == 7 ? $31 : 1/0) with lines ti "main", \ + "telemetry.csv" using (90-$33):($7 == 8 ? $31 : 1/0) with lines ti "landed" + + */ + + diff --git a/altosui/AltosHexfile.java b/altosui/AltosHexfile.java new file mode 100644 index 00000000..d52b46c3 --- /dev/null +++ b/altosui/AltosHexfile.java @@ -0,0 +1,253 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.LinkedList; +import java.util.Iterator; +import java.util.Arrays; +import org.altusmetrum.AltosLib.*; + +class HexFileInputStream extends PushbackInputStream { + public int line; + + public HexFileInputStream(FileInputStream o) { + super(new BufferedInputStream(o)); + line = 1; + } + + public int read() throws IOException { + int c = super.read(); + if (c == '\n') + line++; + return c; + } + + public void unread(int c) throws IOException { + if (c == '\n') + line--; + if (c != -1) + super.unread(c); + } +} + +class HexRecord implements Comparable { + public int address; + public int type; + public byte checksum; + public byte[] data; + + static final int NORMAL = 0; + static final int EOF = 1; + static final int EXTENDED_ADDRESS = 2; + + enum read_state { + marker, + length, + address, + type, + data, + checksum, + newline, + white, + done, + } + + boolean ishex(int c) { + if ('0' <= c && c <= '9') + return true; + if ('a' <= c && c <= 'f') + return true; + if ('A' <= c && c <= 'F') + return true; + return false; + } + + boolean isspace(int c) { + switch (c) { + case ' ': + case '\t': + return true; + } + return false; + } + + int fromhex(int c) { + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + return -1; + } + + public byte checksum() { + byte got = 0; + + got += data.length; + got += (address >> 8) & 0xff; + got += (address ) & 0xff; + got += type; + for (int i = 0; i < data.length; i++) + got += data[i]; + return (byte) (-got); + } + + public int compareTo(Object other) { + HexRecord o = (HexRecord) other; + return address - o.address; + } + + public String toString() { + return String.format("%04x: %02x (%d)", address, type, data.length); + } + + public HexRecord(HexFileInputStream input) throws IOException { + read_state state = read_state.marker; + int nhexbytes = 0; + int hex = 0; + int ndata = 0; + byte got_checksum; + + while (state != read_state.done) { + int c = input.read(); + if (c < 0 && state != read_state.white) + throw new IOException(String.format("%d: Unexpected EOF", input.line)); + if (c == ' ') + continue; + switch (state) { + case marker: + if (c != ':') + throw new IOException("Missing ':'"); + state = read_state.length; + nhexbytes = 2; + hex = 0; + break; + case length: + case address: + case type: + case data: + case checksum: + if(!ishex(c)) + throw new IOException(String.format("Non-hex char '%c'", c)); + hex = hex << 4 | fromhex(c); + --nhexbytes; + if (nhexbytes != 0) + break; + + switch (state) { + case length: + data = new byte[hex]; + state = read_state.address; + nhexbytes = 4; + break; + case address: + address = hex; + state = read_state.type; + nhexbytes = 2; + break; + case type: + type = hex; + if (data.length > 0) + state = read_state.data; + else + state = read_state.checksum; + nhexbytes = 2; + ndata = 0; + break; + case data: + data[ndata] = (byte) hex; + ndata++; + nhexbytes = 2; + if (ndata == data.length) + state = read_state.checksum; + break; + case checksum: + checksum = (byte) hex; + state = read_state.newline; + break; + default: + break; + } + hex = 0; + break; + case newline: + if (c != '\n' && c != '\r') + throw new IOException("Missing newline"); + state = read_state.white; + break; + case white: + if (!isspace(c)) { + input.unread(c); + state = read_state.done; + } + break; + case done: + break; + } + } + got_checksum = checksum(); + if (got_checksum != checksum) + throw new IOException(String.format("Invalid checksum (read 0x%02x computed 0x%02x)\n", + checksum, got_checksum)); + } +} + +public class AltosHexfile { + public int address; + public byte[] data; + + public byte get_byte(int a) { + return data[a - address]; + } + + public AltosHexfile(FileInputStream file) throws IOException { + HexFileInputStream input = new HexFileInputStream(file); + LinkedList<HexRecord> record_list = new LinkedList<HexRecord>(); + boolean done = false; + + while (!done) { + HexRecord record = new HexRecord(input); + + if (record.type == HexRecord.EOF) + done = true; + else + record_list.add(record); + } + HexRecord[] records = record_list.toArray(new HexRecord[0]); + Arrays.sort(records); + if (records.length > 0) { + int base = records[0].address; + int bound = records[records.length-1].address + + records[records.length-1].data.length; + + data = new byte[bound - base]; + address = base; + Arrays.fill(data, (byte) 0xff); + + /* Paint the records into the new array */ + for (int i = 0; i < records.length; i++) { + for (int j = 0; j < records[i].data.length; j++) + data[records[i].address - base + j] = records[i].data[j]; + } + } + } +}
\ No newline at end of file diff --git a/altosui/AltosIdleMonitorUI.java b/altosui/AltosIdleMonitorUI.java new file mode 100644 index 00000000..46ca3e5d --- /dev/null +++ b/altosui/AltosIdleMonitorUI.java @@ -0,0 +1,190 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosIdleMonitorUI extends AltosFrame implements AltosFlightDisplay, AltosFontListener, AltosIdleMonitorListener { + AltosDevice device; + JTabbedPane pane; + AltosPad pad; + AltosInfoTable flightInfo; + AltosFlightStatus flightStatus; + AltosIdleMonitor thread; + int serial; + boolean remote; + + void stop_display() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + } + + void disconnect() { + stop_display(); + } + + public void reset() { + pad.reset(); + flightInfo.clear(); + } + + public void set_font() { + pad.set_font(); + flightInfo.set_font(); + } + + public void font_size_changed(int font_size) { + set_font(); + } + + AltosFlightStatusUpdate status_update; + + public void show(AltosState state, int crc_errors) { + status_update.saved_state = state; + try { + pad.show(state, crc_errors); + flightStatus.show(state, crc_errors); + flightInfo.show(state, crc_errors); + } catch (Exception e) { + System.out.print("Show exception" + e); + } + } + + public void update(final AltosState state) { + Runnable r = new Runnable() { + public void run() { + show(state, 0); + } + }; + SwingUtilities.invokeLater(r); + } + + Container bag; + AltosFreqList frequencies; + + public AltosIdleMonitorUI(JFrame in_owner) + throws FileNotFoundException, AltosSerialInUseException, TimeoutException, InterruptedException { + + device = AltosDeviceDialog.show(in_owner, Altos.product_any); + remote = false; + if (!device.matchProduct(Altos.product_altimeter)) + remote = true; + + serial = device.getSerial(); + bag = getContentPane(); + bag.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + + java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg"); + if (imgURL != null) + setIconImage(new ImageIcon(imgURL).getImage()); + + setTitle(String.format("AltOS %s", device.toShortString())); + + /* Stick frequency selector at top of table for telemetry monitoring */ + if (remote && serial >= 0) { + // Frequency menu + frequencies = new AltosFreqList(AltosUIPreferences.frequency(serial)); + frequencies.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + double frequency = frequencies.frequency(); + thread.set_frequency(frequency); + AltosUIPreferences.set_frequency(device.getSerial(), + frequency); + } + }); + c.gridx = 0; + c.gridy = 0; + c.insets = new Insets(3, 3, 3, 3); + c.anchor = GridBagConstraints.WEST; + bag.add (frequencies, c); + } + + + /* Flight status is always visible */ + flightStatus = new AltosFlightStatus(); + c.gridx = 0; + c.gridy = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridwidth = 2; + bag.add(flightStatus, c); + c.gridwidth = 1; + + /* The rest of the window uses a tabbed pane to + * show one of the alternate data views + */ + pane = new JTabbedPane(); + + pad = new AltosPad(); + pane.add("Launch Pad", pad); + + flightInfo = new AltosInfoTable(); + pane.add("Table", new JScrollPane(flightInfo)); + + /* Make the tabbed pane use the rest of the window space */ + c.gridx = 0; + c.gridy = 2; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + c.gridwidth = 2; + bag.add(pane, c); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + AltosUIPreferences.register_font_listener(this); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + disconnect(); + setVisible(false); + dispose(); + AltosUIPreferences.unregister_font_listener(AltosIdleMonitorUI.this); + } + }); + + pack(); + setVisible(true); + + thread = new AltosIdleMonitor((AltosIdleMonitorListener) this, (AltosLink) new AltosSerial (device), (boolean) remote); + + status_update = new AltosFlightStatusUpdate(flightStatus); + + new javax.swing.Timer(100, status_update).start(); + + thread.start(); + } +} diff --git a/altosui/AltosIgniteUI.java b/altosui/AltosIgniteUI.java new file mode 100644 index 00000000..78eba8e6 --- /dev/null +++ b/altosui/AltosIgniteUI.java @@ -0,0 +1,423 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosIgniteUI + extends AltosDialog + implements ActionListener +{ + AltosDevice device; + JFrame owner; + JLabel label; + JRadioButton apogee; + JLabel apogee_status_label; + JRadioButton main; + JLabel main_status_label; + JToggleButton arm; + JButton fire; + javax.swing.Timer timer; + JButton close; + + int apogee_status; + int main_status; + + final static int timeout = 1 * 1000; + + int time_remaining; + boolean timer_running; + + LinkedBlockingQueue<String> command_queue; + + class IgniteHandler implements Runnable { + AltosIgnite ignite; + JFrame owner; + + void send_exception(Exception e) { + final Exception f_e = e; + Runnable r = new Runnable() { + public void run() { + ignite_exception(f_e); + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + try { + AltosSerial serial = new AltosSerial(device); + serial.set_frame(owner); + ignite = new AltosIgnite(serial, + !device.matchProduct(Altos.product_altimeter)); + + } catch (Exception e) { + send_exception(e); + return; + } + + for (;;) { + Runnable r; + + try { + String command = command_queue.take(); + String reply = null; + + if (command.equals("get_status")) { + apogee_status = ignite.status(AltosIgnite.Apogee); + main_status = ignite.status(AltosIgnite.Main); + reply = "status"; + } else if (command.equals("main")) { + ignite.fire(AltosIgnite.Main); + reply = "fired"; + } else if (command.equals("apogee")) { + ignite.fire(AltosIgnite.Apogee); + reply = "fired"; + } else if (command.equals("quit")) { + ignite.close(); + break; + } else { + throw new ParseException(String.format("invalid command %s", command), 0); + } + final String f_reply = reply; + r = new Runnable() { + public void run() { + ignite_reply(f_reply); + } + }; + SwingUtilities.invokeLater(r); + } catch (Exception e) { + send_exception(e); + } + } + } + + public IgniteHandler(JFrame in_owner) { + owner = in_owner; + } + } + + void ignite_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + void ignite_reply(String reply) { + if (reply.equals("status")) { + set_ignite_status(); + } else if (reply.equals("fired")) { + fired(); + } + } + + void set_arm_text() { + if (arm.isSelected()) + arm.setText(String.format("%d", time_remaining)); + else + arm.setText("Arm"); + } + + void start_timer() { + time_remaining = 10; + set_arm_text(); + timer_running = true; + } + + void stop_timer() { + time_remaining = 0; + arm.setSelected(false); + arm.setEnabled(false); + fire.setEnabled(false); + timer_running = false; + set_arm_text(); + } + + void cancel () { + apogee.setSelected(false); + main.setSelected(false); + fire.setEnabled(false); + stop_timer(); + } + + void send_command(String command) { + try { + command_queue.put(command); + } catch (Exception ex) { + ignite_exception(ex); + } + } + + boolean getting_status = false; + + boolean visible = false; + void set_ignite_status() { + getting_status = false; + apogee_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(apogee_status))); + main_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(main_status))); + if (!visible) { + visible = true; + setVisible(true); + } + } + + void poll_ignite_status() { + if (!getting_status) { + getting_status = true; + send_command("get_status"); + } + } + + boolean firing = false; + + void start_fire(String which) { + if (!firing) { + firing = true; + send_command(which); + } + } + + void fired() { + firing = false; + cancel(); + } + + void close() { + send_command("quit"); + timer.stop(); + setVisible(false); + dispose(); + } + + void tick_timer() { + if (timer_running) { + --time_remaining; + if (time_remaining <= 0) + cancel(); + else + set_arm_text(); + } + poll_ignite_status(); + } + + void fire() { + if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) { + String igniter = "none"; + if (apogee.isSelected() && !main.isSelected()) + igniter = "apogee"; + else if (main.isSelected() && !apogee.isSelected()) + igniter = "main"; + send_command(igniter); + cancel(); + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("apogee") || cmd.equals("main")) { + stop_timer(); + } + + if (cmd.equals("apogee") && apogee.isSelected()) { + main.setSelected(false); + arm.setEnabled(true); + } + if (cmd.equals("main") && main.isSelected()) { + apogee.setSelected(false); + arm.setEnabled(true); + } + + if (cmd.equals("arm")) { + if (arm.isSelected()) { + fire.setEnabled(true); + start_timer(); + } else + cancel(); + } + if (cmd.equals("fire")) + fire(); + if (cmd.equals("tick")) + tick_timer(); + if (cmd.equals("close")) { + close(); + } + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosIgniteUI ui; + + public ConfigListener(AltosIgniteUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + command_queue = new LinkedBlockingQueue<String>(); + + device = AltosDeviceDialog.show(owner, Altos.product_any); + if (device != null) { + IgniteHandler handler = new IgniteHandler(owner); + Thread t = new Thread(handler); + t.start(); + return true; + } + return false; + } + + public AltosIgniteUI(JFrame in_owner) { + + owner = in_owner; + apogee_status = AltosIgnite.Unknown; + main_status = AltosIgnite.Unknown; + + if (!open()) + return; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + timer = new javax.swing.Timer(timeout, this); + timer.setActionCommand("tick"); + timer_running = false; + timer.restart(); + + owner = in_owner; + + pane.setLayout(new GridBagLayout()); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + label = new JLabel ("Fire Igniter"); + pane.add(label, c); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + apogee = new JRadioButton ("Apogee"); + pane.add(apogee, c); + apogee.addActionListener(this); + apogee.setActionCommand("apogee"); + + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + apogee_status_label = new JLabel(); + pane.add(apogee_status_label, c); + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + main = new JRadioButton ("Main"); + pane.add(main, c); + main.addActionListener(this); + main.setActionCommand("main"); + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + main_status_label = new JLabel(); + pane.add(main_status_label, c); + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + arm = new JToggleButton ("Arm"); + pane.add(arm, c); + arm.addActionListener(this); + arm.setActionCommand("arm"); + arm.setEnabled(false); + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + fire = new JButton ("Fire"); + fire.setEnabled(false); + pane.add(fire, c); + fire.addActionListener(this); + fire.setActionCommand("fire"); + + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + close = new JButton ("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("close"); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + } +}
\ No newline at end of file diff --git a/altosui/AltosInfoTable.java b/altosui/AltosInfoTable.java new file mode 100644 index 00000000..c1400976 --- /dev/null +++ b/altosui/AltosInfoTable.java @@ -0,0 +1,211 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosInfoTable extends JTable { + private AltosFlightInfoTableModel model; + + static final int info_columns = 3; + static final int info_rows = 17; + + int desired_row_height() { + FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font); + return (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 18 / 10; + } + + int text_width(String t) { + FontMetrics infoValueMetrics = getFontMetrics(Altos.table_value_font); + + return infoValueMetrics.stringWidth(t); + } + + void set_layout() { + setRowHeight(desired_row_height()); + for (int i = 0; i < info_columns * 2; i++) + { + TableColumn column = getColumnModel().getColumn(i); + + if ((i & 1) == 0) + column.setPreferredWidth(text_width(" Satellites Visible ")); + else + column.setPreferredWidth(text_width(" 179°59.99999' ")); + } + } + + public AltosInfoTable() { + super(new AltosFlightInfoTableModel(info_rows, info_columns)); + model = (AltosFlightInfoTableModel) getModel(); + setFont(Altos.table_value_font); + setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS); + setShowGrid(true); + set_layout(); + doLayout(); + } + + public void set_font() { + setFont(Altos.table_value_font); + set_layout(); + doLayout(); + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + void info_reset() { + model.reset(); + } + + void info_add_row(int col, String name, String value) { + model.addRow(col, name, value); + } + + void info_add_row(int col, String name, String format, Object... parameters) { + info_add_row (col, name, String.format(format, parameters)); + } + + void info_add_deg(int col, String name, double v, int pos, int neg) { + int c = pos; + if (v < 0) { + c = neg; + v = -v; + } + double deg = Math.floor(v); + double min = (v - deg) * 60; + + info_add_row(col, name, String.format("%3.0f°%08.5f'", deg, min)); + } + + void info_finish() { + model.finish(); + } + + public void clear() { + model.clear(); + } + + public void show(AltosState state, int crc_errors) { + if (state == null) + return; + info_reset(); + info_add_row(0, "Altitude", "%6.0f m", state.altitude); + info_add_row(0, "Pad altitude", "%6.0f m", state.ground_altitude); + info_add_row(0, "Height", "%6.0f m", state.height); + info_add_row(0, "Max height", "%6.0f m", state.max_height); + info_add_row(0, "Acceleration", "%8.1f m/s²", state.acceleration); + info_add_row(0, "Max acceleration", "%8.1f m/s²", state.max_acceleration); + info_add_row(0, "Speed", "%8.1f m/s", state.ascent ? state.speed : state.baro_speed); + info_add_row(0, "Max Speed", "%8.1f m/s", state.max_speed); + info_add_row(0, "Temperature", "%9.2f °C", state.temperature); + info_add_row(0, "Battery", "%9.2f V", state.battery); + if (state.drogue_sense != AltosRecord.MISSING) + info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense); + if (state.main_sense != AltosRecord.MISSING) + info_add_row(0, "Main", "%9.2f V", state.main_sense); + info_add_row(0, "CRC Errors", "%6d", crc_errors); + + if (state.gps == null || !state.gps.connected) { + info_add_row(1, "GPS", "not available"); + } else { + if (state.gps_ready) + info_add_row(1, "GPS state", "%s", "ready"); + else + info_add_row(1, "GPS state", "wait (%d)", + state.gps_waiting); + if (state.data.gps.locked) + info_add_row(1, "GPS", " locked"); + else if (state.data.gps.connected) + info_add_row(1, "GPS", " unlocked"); + else + info_add_row(1, "GPS", " missing"); + info_add_row(1, "Satellites", "%6d", state.data.gps.nsat); + info_add_deg(1, "Latitude", state.gps.lat, 'N', 'S'); + info_add_deg(1, "Longitude", state.gps.lon, 'E', 'W'); + info_add_row(1, "GPS altitude", "%6d", state.gps.alt); + info_add_row(1, "GPS height", "%6.0f", state.gps_height); + + /* The SkyTraq GPS doesn't report these values */ + if (false) { + info_add_row(1, "GPS ground speed", "%8.1f m/s %3d°", + state.gps.ground_speed, + state.gps.course); + info_add_row(1, "GPS climb rate", "%8.1f m/s", + state.gps.climb_rate); + info_add_row(1, "GPS error", "%6d m(h)%3d m(v)", + state.gps.h_error, state.gps.v_error); + } + info_add_row(1, "GPS hdop", "%8.1f", state.gps.hdop); + + if (state.npad > 0) { + if (state.from_pad != null) { + info_add_row(1, "Distance from pad", "%6d m", + (int) (state.from_pad.distance + 0.5)); + info_add_row(1, "Direction from pad", "%6d°", + (int) (state.from_pad.bearing + 0.5)); + info_add_row(1, "Elevation from pad", "%6d°", + (int) (state.elevation + 0.5)); + info_add_row(1, "Range from pad", "%6d m", + (int) (state.range + 0.5)); + } else { + info_add_row(1, "Distance from pad", "unknown"); + info_add_row(1, "Direction from pad", "unknown"); + info_add_row(1, "Elevation from pad", "unknown"); + info_add_row(1, "Range from pad", "unknown"); + } + info_add_deg(1, "Pad latitude", state.pad_lat, 'N', 'S'); + info_add_deg(1, "Pad longitude", state.pad_lon, 'E', 'W'); + info_add_row(1, "Pad GPS alt", "%6.0f m", state.pad_alt); + } + info_add_row(1, "GPS date", "%04d-%02d-%02d", + state.gps.year, + state.gps.month, + state.gps.day); + info_add_row(1, "GPS time", " %02d:%02d:%02d", + state.gps.hour, + state.gps.minute, + state.gps.second); + int nsat_vis = 0; + int c; + + if (state.gps.cc_gps_sat == null) + info_add_row(2, "Satellites Visible", "%4d", 0); + else { + info_add_row(2, "Satellites Visible", "%4d", state.gps.cc_gps_sat.length); + for (c = 0; c < state.gps.cc_gps_sat.length; c++) { + info_add_row(2, "Satellite id,C/N0", + "%4d, %4d", + state.gps.cc_gps_sat[c].svid, + state.gps.cc_gps_sat[c].c_n0); + } + } + } + info_finish(); + } +} diff --git a/altosui/AltosKML.java b/altosui/AltosKML.java new file mode 100644 index 00000000..ff0734b8 --- /dev/null +++ b/altosui/AltosKML.java @@ -0,0 +1,176 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + +public class AltosKML implements AltosWriter { + + File name; + PrintStream out; + int state = -1; + AltosRecord prev = null; + + static final String[] kml_state_colors = { + "FF000000", + "FF000000", + "FF000000", + "FF0000FF", + "FF4080FF", + "FF00FFFF", + "FFFF0000", + "FF00FF00", + "FF000000", + "FFFFFFFF" + }; + + static final String kml_header_start = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n" + + "<Document>\n" + + " <name>AO Flight#%d S/N: %03d</name>\n" + + " <description>\n"; + static final String kml_header_end = + " </description>\n" + + " <open>0</open>\n"; + + static final String kml_style_start = + " <Style id=\"ao-flightstate-%s\">\n" + + " <LineStyle><color>%s</color><width>4</width></LineStyle>\n" + + " <BalloonStyle>\n" + + " <text>\n"; + + static final String kml_style_end = + " </text>\n" + + " </BalloonStyle>\n" + + " </Style>\n"; + + static final String kml_placemark_start = + " <Placemark>\n" + + " <name>%s</name>\n" + + " <styleUrl>#ao-flightstate-%s</styleUrl>\n" + + " <LineString>\n" + + " <tessellate>1</tessellate>\n" + + " <altitudeMode>absolute</altitudeMode>\n" + + " <coordinates>\n"; + + static final String kml_coord_fmt = + " %.7f,%.7f,%.7f <!-- alt %12.7f time %12.7f sats %d -->\n"; + + static final String kml_placemark_end = + " </coordinates>\n" + + " </LineString>\n" + + " </Placemark>\n"; + + static final String kml_footer = + "</Document>\n" + + "</kml>\n"; + + void start (AltosRecord record) { + out.printf(kml_header_start, record.flight, record.serial); + out.printf("Date: %04d-%02d-%02d\n", + record.gps.year, record.gps.month, record.gps.day); + out.printf("Time: %2d:%02d:%02d\n", + record.gps.hour, record.gps.minute, record.gps.second); + out.printf("%s", kml_header_end); + } + + boolean started = false; + + void state_start(AltosRecord record) { + String state_name = Altos.state_name(record.state); + out.printf(kml_style_start, state_name, kml_state_colors[record.state]); + out.printf("\tState: %s\n", state_name); + out.printf("%s", kml_style_end); + out.printf(kml_placemark_start, state_name, state_name); + } + + void state_end(AltosRecord record) { + out.printf("%s", kml_placemark_end); + } + + void coord(AltosRecord record) { + AltosGPS gps = record.gps; + out.printf(kml_coord_fmt, + gps.lon, gps.lat, + record.filtered_altitude(), (double) gps.alt, + record.time, gps.nsat); + } + + void end() { + out.printf("%s", kml_footer); + } + + public void close() { + if (prev != null) { + state_end(prev); + end(); + prev = null; + } + } + + public void write(AltosRecord record) { + AltosGPS gps = record.gps; + + if (gps == null) + return; + if ((record.seen & (AltosRecord.seen_flight)) == 0) + return; + if ((record.seen & (AltosRecord.seen_gps_lat)) == 0) + return; + if ((record.seen & (AltosRecord.seen_gps_lon)) == 0) + return; + if (!started) { + start(record); + started = true; + } + if (prev != null && + prev.gps.second == record.gps.second && + prev.gps.minute == record.gps.minute && + prev.gps.hour == record.gps.hour) + return; + if (record.state != state) { + state = record.state; + if (prev != null) { + coord(record); + state_end(prev); + } + state_start(record); + } + coord(record); + prev = record; + } + + public void write(AltosRecordIterable iterable) { + for (AltosRecord record : iterable) + write(record); + } + + public AltosKML(File in_name) throws FileNotFoundException { + name = in_name; + out = new PrintStream(name); + } + + public AltosKML(String in_string) throws FileNotFoundException { + this(new File(in_string)); + } +} diff --git a/altosui/AltosLanded.java b/altosui/AltosLanded.java new file mode 100644 index 00000000..a47e1cbd --- /dev/null +++ b/altosui/AltosLanded.java @@ -0,0 +1,302 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosLanded extends JComponent implements AltosFlightDisplay, ActionListener { + GridBagLayout layout; + + public class LandedValue { + JLabel label; + JTextField value; + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + + void show() { + label.setVisible(true); + value.setVisible(true); + } + + public void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + } + + void show(String format, double v) { + show(); + value.setText(String.format(format, v)); + } + + + public LandedValue (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 0; c.gridy = y; + c.insets = new Insets(10, 10, 10, 10); + c.anchor = GridBagConstraints.WEST; + c.weightx = 0; + c.fill = GridBagConstraints.VERTICAL; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 1; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.weightx = 1; + c.fill = GridBagConstraints.BOTH; + layout.setConstraints(value, c); + add(value); + } + } + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %4d° %9.6f", h, deg, min); + } + + class Lat extends LandedValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.gps != null && state.gps.connected) + value.setText(pos(state.gps.lat,"N", "S")); + else + value.setText("???"); + } + public Lat (GridBagLayout layout, int y) { + super (layout, y, "Latitude"); + } + } + + Lat lat; + + class Lon extends LandedValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.gps != null && state.gps.connected) + value.setText(pos(state.gps.lon,"E", "W")); + else + value.setText("???"); + } + public Lon (GridBagLayout layout, int y) { + super (layout, y, "Longitude"); + } + } + + Lon lon; + + class Bearing extends LandedValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.from_pad != null) + show("%3.0f°", state.from_pad.bearing); + else + value.setText("???"); + } + public Bearing (GridBagLayout layout, int y) { + super (layout, y, "Bearing"); + } + } + + Bearing bearing; + + class Distance extends LandedValue { + void show (AltosState state, int crc_errors) { + show(); + if (state.from_pad != null) + show("%6.0f m", state.from_pad.distance); + else + value.setText("???"); + } + public Distance (GridBagLayout layout, int y) { + super (layout, y, "Distance"); + } + } + + Distance distance; + + class Height extends LandedValue { + void show (AltosState state, int crc_errors) { + show("%6.0f m", state.max_height); + } + public Height (GridBagLayout layout, int y) { + super (layout, y, "Maximum Height"); + } + } + + Height height; + + class Speed extends LandedValue { + void show (AltosState state, int crc_errors) { + show("%6.0f m/s", state.max_speed); + } + public Speed (GridBagLayout layout, int y) { + super (layout, y, "Maximum Speed"); + } + } + + Speed speed; + + class Accel extends LandedValue { + void show (AltosState state, int crc_errors) { + show("%6.0f m/s²", state.max_acceleration); + } + public Accel (GridBagLayout layout, int y) { + super (layout, y, "Maximum Acceleration"); + } + } + + Accel accel; + + public void reset() { + lat.reset(); + lon.reset(); + bearing.reset(); + distance.reset(); + height.reset(); + speed.reset(); + accel.reset(); + } + + public void set_font() { + lat.set_font(); + lon.set_font(); + bearing.set_font(); + distance.set_font(); + height.set_font(); + speed.set_font(); + accel.set_font(); + } + + public void show(AltosState state, int crc_errors) { + if (state.gps != null && state.gps.connected) { + bearing.show(state, crc_errors); + distance.show(state, crc_errors); + lat.show(state, crc_errors); + lon.show(state, crc_errors); + } else { + bearing.hide(); + distance.hide(); + lat.hide(); + lon.hide(); + } + height.show(state, crc_errors); + speed.show(state, crc_errors); + accel.show(state, crc_errors); + if (reader.backing_file() != null) + graph.setEnabled(true); + } + + JButton graph; + AltosFlightReader reader; + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("graph")) { + File file = reader.backing_file(); + if (file != null) { + String filename = file.getName(); + try { + AltosRecordIterable records = null; + if (filename.endsWith("eeprom")) { + FileInputStream in = new FileInputStream(file); + records = new AltosEepromIterable(in); + } else if (filename.endsWith("telem")) { + FileInputStream in = new FileInputStream(file); + records = new AltosTelemetryIterable(in); + } else { + throw new FileNotFoundException(filename); + } + try { + new AltosGraphUI(records, filename); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } catch (FileNotFoundException fe) { + JOptionPane.showMessageDialog(null, + fe.getMessage(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } + } + } + } + + public AltosLanded(AltosFlightReader in_reader) { + layout = new GridBagLayout(); + + reader = in_reader; + + setLayout(layout); + + /* Elements in descent display */ + bearing = new Bearing(layout, 0); + distance = new Distance(layout, 1); + lat = new Lat(layout, 2); + lon = new Lon(layout, 3); + height = new Height(layout, 4); + speed = new Speed(layout, 5); + accel = new Accel(layout, 6); + + graph = new JButton ("Graph Flight"); + graph.setActionCommand("graph"); + graph.addActionListener(this); + graph.setEnabled(false); + + GridBagConstraints c = new GridBagConstraints(); + + c.gridx = 0; c.gridy = 7; + c.insets = new Insets(10, 10, 10, 10); + c.anchor = GridBagConstraints.WEST; + c.weightx = 0; + c.weighty = 0; + c.fill = GridBagConstraints.VERTICAL; + add(graph, c); + } +} diff --git a/altosui/AltosLaunch.java b/altosui/AltosLaunch.java new file mode 100644 index 00000000..0e493b91 --- /dev/null +++ b/altosui/AltosLaunch.java @@ -0,0 +1,202 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.io.*; +import java.util.concurrent.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import org.altusmetrum.AltosLib.*; + +public class AltosLaunch { + AltosDevice device; + AltosSerial serial; + boolean serial_started; + int launcher_serial; + int launcher_channel; + int rssi; + + final static int Unknown = -1; + final static int Good = 0; + final static int Bad = 1; + + int armed; + int igniter; + + private void start_serial() throws InterruptedException { + serial_started = true; + } + + private void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (serial == null) + return; + } + + class string_ref { + String value; + + public String get() { + return value; + } + public void set(String i) { + value = i; + } + public string_ref() { + value = null; + } + } + + private boolean get_string(String line, String label, string_ref s) { + if (line.startsWith(label)) { + String quoted = line.substring(label.length()).trim(); + + if (quoted.startsWith("\"")) + quoted = quoted.substring(1); + if (quoted.endsWith("\"")) + quoted = quoted.substring(0,quoted.length()-1); + s.set(quoted); + return true; + } else { + return false; + } + } + + public boolean status() throws InterruptedException, TimeoutException { + boolean ok = false; + if (serial == null) + return false; + string_ref status_name = new string_ref(); + start_serial(); + serial.printf("l %d %d\n", launcher_serial, launcher_channel); + for (;;) { + String line = serial.get_reply(20000); + if (line == null) + throw new TimeoutException(); + if (get_string(line, "Rssi: ", status_name)) { + try { + rssi = Altos.fromdec(status_name.get()); + } catch (NumberFormatException ne) { + } + break; + } else if (get_string(line, "Armed: ", status_name)) { + armed = Good; + String status = status_name.get(); + if (status.startsWith("igniter good")) + igniter = Good; + else if (status.startsWith("igniter bad")) + igniter = Bad; + else + igniter = Unknown; + ok = true; + } else if (get_string(line, "Disarmed: ", status_name)) { + armed = Bad; + if (status_name.get().startsWith("igniter good")) + igniter = Good; + else if (status_name.get().startsWith("igniter bad")) + igniter = Bad; + else + igniter = Unknown; + ok = true; + } else if (get_string(line, "Error ", status_name)) { + armed = Unknown; + igniter = Unknown; + ok = false; + break; + } + } + stop_serial(); + if (!ok) { + armed = Unknown; + igniter = Unknown; + } + return ok; + } + + public static String status_string(int status) { + switch (status) { + case Good: + return "good"; + case Bad: + return "open"; + } + return "unknown"; + } + + public void arm() { + if (serial == null) + return; + try { + start_serial(); + serial.printf("a %d %d\n", launcher_serial, launcher_channel); + serial.flush_output(); + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void fire() { + if (serial == null) + return; + try { + start_serial(); + serial.printf("i %d %d\n", launcher_serial, launcher_channel); + serial.flush_output(); + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void close() { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + serial.close(); + serial = null; + } + + public void set_frame(Frame frame) { + serial.set_frame(frame); + } + + public void set_remote(int in_serial, int in_channel) { + launcher_serial = in_serial; + launcher_channel = in_channel; + } + + public AltosLaunch(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + + device = in_device; + serial = new AltosSerial(device); + } +}
\ No newline at end of file diff --git a/altosui/AltosLaunchUI.java b/altosui/AltosLaunchUI.java new file mode 100644 index 00000000..44481544 --- /dev/null +++ b/altosui/AltosLaunchUI.java @@ -0,0 +1,517 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +class FireButton extends JButton { + protected void processMouseEvent(MouseEvent e) { + super.processMouseEvent(e); + switch (e.getID()) { + case MouseEvent.MOUSE_PRESSED: + if (actionListener != null) + actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_down")); + break; + case MouseEvent.MOUSE_RELEASED: + if (actionListener != null) + actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_up")); + break; + } + } + + public FireButton(String s) { + super(s); + } +} + +public class AltosLaunchUI + extends AltosDialog + implements ActionListener +{ + AltosDevice device; + JFrame owner; + JLabel label; + + int radio_channel; + JLabel radio_channel_label; + JTextField radio_channel_text; + + int launcher_serial; + JLabel launcher_serial_label; + JTextField launcher_serial_text; + + int launcher_channel; + JLabel launcher_channel_label; + JTextField launcher_channel_text; + + JLabel armed_label; + JLabel armed_status_label; + JLabel igniter; + JLabel igniter_status_label; + JToggleButton arm; + FireButton fire; + javax.swing.Timer arm_timer; + javax.swing.Timer fire_timer; + + boolean firing; + boolean armed; + int armed_status; + int igniter_status; + int rssi; + + final static int arm_timeout = 1 * 1000; + final static int fire_timeout = 250; + + int armed_count; + + LinkedBlockingQueue<String> command_queue; + + class LaunchHandler implements Runnable { + AltosLaunch launch; + JFrame owner; + + void send_exception(Exception e) { + final Exception f_e = e; + Runnable r = new Runnable() { + public void run() { + launch_exception(f_e); + } + }; + SwingUtilities.invokeLater(r); + } + + public void run () { + try { + launch = new AltosLaunch(device); + } catch (Exception e) { + send_exception(e); + return; + } + launch.set_frame(owner); + launch.set_remote(launcher_serial, launcher_channel); + + for (;;) { + Runnable r; + + try { + String command = command_queue.take(); + String reply = null; + + if (command.equals("get_status")) { + launch.status(); + reply = "status"; + armed_status = launch.armed; + igniter_status = launch.igniter; + rssi = launch.rssi; + } else if (command.equals("set_remote")) { + launch.set_remote(launcher_serial, launcher_channel); + reply = "remote set"; + } else if (command.equals("arm")) { + launch.arm(); + reply = "armed"; + } else if (command.equals("fire")) { + launch.fire(); + reply = "fired"; + } else if (command.equals("quit")) { + launch.close(); + break; + } else { + throw new ParseException(String.format("invalid command %s", command), 0); + } + final String f_reply = reply; + r = new Runnable() { + public void run() { + launch_reply(f_reply); + } + }; + SwingUtilities.invokeLater(r); + } catch (Exception e) { + send_exception(e); + } + } + } + + public LaunchHandler(JFrame in_owner) { + owner = in_owner; + } + } + + void launch_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + void launch_reply(String reply) { + if (reply == null) + return; + if (reply.equals("remote set")) + poll_launch_status(); + if (reply.equals("status")) { + set_launch_status(); + } + } + + void set_arm_text() { + if (arm.isSelected()) + arm.setText(String.format("%d", armed_count)); + else + arm.setText("Arm"); + } + + void start_arm_timer() { + armed_count = 30; + set_arm_text(); + } + + void stop_arm_timer() { + armed_count = 0; + armed = false; + arm.setSelected(false); + fire.setEnabled(false); + set_arm_text(); + } + + void cancel () { + fire.setEnabled(false); + firing = false; + stop_arm_timer(); + } + + void send_command(String command) { + try { + command_queue.put(command); + } catch (Exception ex) { + launch_exception(ex); + } + } + + boolean getting_status = false; + + void set_launch_status() { + getting_status = false; + armed_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(armed_status))); + igniter_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(igniter_status))); + } + + void poll_launch_status() { + if (!getting_status && !firing && !armed) { + getting_status = true; + send_command("get_status"); + } + } + + void fired() { + firing = false; + cancel(); + } + + void close() { + send_command("quit"); + arm_timer.stop(); + setVisible(false); + dispose(); + } + + void tick_arm_timer() { + if (armed_count > 0) { + --armed_count; + if (armed_count <= 0) { + armed_count = 0; + cancel(); + } else { + if (!firing) { + send_command("arm"); + set_arm_text(); + } + } + } + poll_launch_status(); + } + + void arm() { + if (arm.isSelected()) { + fire.setEnabled(true); + start_arm_timer(); + if (!firing) + send_command("arm"); + armed = true; + } else + cancel(); + } + + void fire_more() { + if (firing) + send_command("fire"); + } + + void fire_down() { + if (arm.isEnabled() && arm.isSelected() && armed_count > 0) { + firing = true; + fire_more(); + fire_timer.restart(); + } + } + + void fire_up() { + firing = false; + fire_timer.stop(); + } + + void set_radio() { + try { + radio_channel = Integer.parseInt(radio_channel_text.getText()); + } catch (NumberFormatException ne) { + radio_channel_text.setText(String.format("%d", radio_channel)); + } + } + + void set_serial() { + try { + launcher_serial = Integer.parseInt(launcher_serial_text.getText()); + AltosUIPreferences.set_launcher_serial(launcher_serial); + send_command("set_remote"); + } catch (NumberFormatException ne) { + launcher_serial_text.setText(String.format("%d", launcher_serial)); + } + } + + void set_channel() { + try { + launcher_channel = Integer.parseInt(launcher_channel_text.getText()); + AltosUIPreferences.set_launcher_serial(launcher_channel); + send_command("set_remote"); + } catch (NumberFormatException ne) { + launcher_channel_text.setText(String.format("%d", launcher_channel)); + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("armed") || cmd.equals("igniter")) { + stop_arm_timer(); + } + + if (cmd.equals("arm")) + arm(); + if (cmd.equals("tick_arm")) + tick_arm_timer(); + if (cmd.equals("close")) + close(); + if (cmd.equals("fire_down")) + fire_down(); + if (cmd.equals("fire_up")) + fire_up(); + if (cmd.equals("tick_fire")) + fire_more(); + if (cmd.equals("new_serial")) + set_serial(); + if (cmd.equals("new_channel")) + set_channel(); + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosLaunchUI ui; + + public ConfigListener(AltosLaunchUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + command_queue = new LinkedBlockingQueue<String>(); + + device = AltosDeviceDialog.show(owner, Altos.product_any); + if (device != null) { + LaunchHandler handler = new LaunchHandler(owner); + Thread t = new Thread(handler); + t.start(); + return true; + } + return false; + } + + public AltosLaunchUI(JFrame in_owner) { + + launcher_channel = AltosUIPreferences.launcher_channel(); + launcher_serial = AltosUIPreferences.launcher_serial(); + owner = in_owner; + armed_status = AltosLaunch.Unknown; + igniter_status = AltosLaunch.Unknown; + + if (!open()) + return; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + arm_timer = new javax.swing.Timer(arm_timeout, this); + arm_timer.setActionCommand("tick_arm"); + arm_timer.restart(); + + fire_timer = new javax.swing.Timer(fire_timeout, this); + fire_timer.setActionCommand("tick_fire"); + + owner = in_owner; + + pane.setLayout(new GridBagLayout()); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + label = new JLabel ("Launch Controller"); + pane.add(label, c); + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_serial_label = new JLabel("Launcher Serial"); + pane.add(launcher_serial_label, c); + + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_serial_text = new JTextField(7); + launcher_serial_text.setText(String.format("%d", launcher_serial)); + launcher_serial_text.setActionCommand("new_serial"); + launcher_serial_text.addActionListener(this); + pane.add(launcher_serial_text, c); + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_channel_label = new JLabel("Launcher Channel"); + pane.add(launcher_channel_label, c); + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + launcher_channel_text = new JTextField(7); + launcher_channel_text.setText(String.format("%d", launcher_channel)); + launcher_channel_text.setActionCommand("new_channel"); + launcher_channel_text.addActionListener(this); + pane.add(launcher_channel_text, c); + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + armed_label = new JLabel ("Armed"); + pane.add(armed_label, c); + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + armed_status_label = new JLabel(); + pane.add(armed_status_label, c); + + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + igniter = new JLabel ("Igniter"); + pane.add(igniter, c); + + c.gridx = 1; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + igniter_status_label = new JLabel(); + pane.add(igniter_status_label, c); + + c.gridx = 0; + c.gridy = 5; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + arm = new JToggleButton ("Arm"); + pane.add(arm, c); + arm.addActionListener(this); + arm.setActionCommand("arm"); + arm.setEnabled(true); + + c.gridx = 1; + c.gridy = 5; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + fire = new FireButton ("Fire"); + fire.setEnabled(false); + pane.add(fire, c); + fire.addActionListener(this); + fire.setActionCommand("fire"); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + + setVisible(true); + } +}
\ No newline at end of file diff --git a/altosui/AltosLed.java b/altosui/AltosLed.java new file mode 100644 index 00000000..1358cd48 --- /dev/null +++ b/altosui/AltosLed.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosLed extends JLabel { + ImageIcon on, off; + + ImageIcon create_icon(String path) { + java.net.URL imgURL = AltosUI.class.getResource(path); + if (imgURL != null) + return new ImageIcon(imgURL); + System.err.printf("Cannot find icon \"%s\"\n", path); + return null; + } + + public void set(boolean set) { + if (set) + setIcon(on); + else + setIcon(off); + } + + public AltosLed(String on_path, String off_path) { + on = create_icon(on_path); + off = create_icon(off_path); + setIcon(off); + } +} diff --git a/altosui/AltosLights.java b/altosui/AltosLights.java new file mode 100644 index 00000000..8bd9e7de --- /dev/null +++ b/altosui/AltosLights.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosLights extends JComponent { + + GridBagLayout gridbag; + + AltosLed red, green; + + ImageIcon create_icon(String path, String description) { + java.net.URL imgURL = AltosUI.class.getResource(path); + if (imgURL != null) + return new ImageIcon(imgURL, description); + System.err.printf("Cannot find icon \"%s\"\n", path); + return null; + } + + public void set (boolean on) { + if (on) { + red.set(false); + green.set(true); + } else { + red.set(true); + green.set(false); + } + } + + public AltosLights() { + GridBagConstraints c; + gridbag = new GridBagLayout(); + setLayout(gridbag); + + c = new GridBagConstraints(); + red = new AltosLed("/redled.png", "/grayled.png"); + c.gridx = 0; c.gridy = 0; + c.insets = new Insets (0, 5, 0, 5); + gridbag.setConstraints(red, c); + add(red); + red.set(true); + green = new AltosLed("/greenled.png", "/grayled.png"); + c.gridx = 1; c.gridy = 0; + gridbag.setConstraints(green, c); + add(green); + green.set(false); + } +} diff --git a/altosui/AltosPad.java b/altosui/AltosPad.java new file mode 100644 index 00000000..0a3f3d65 --- /dev/null +++ b/altosui/AltosPad.java @@ -0,0 +1,358 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import org.altusmetrum.AltosLib.*; + +public class AltosPad extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public class LaunchStatus { + JLabel label; + JTextField value; + AltosLights lights; + + void show(AltosState state, int crc_errors) {} + void reset() { + value.setText(""); + lights.set(false); + } + + public void show() { + label.setVisible(true); + value.setVisible(true); + lights.setVisible(true); + } + + public void hide() { + label.setVisible(false); + value.setVisible(false); + lights.setVisible(false); + } + + public void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + public LaunchStatus (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + lights = new AltosLights(); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(lights, c); + add(lights); + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + } + } + + public class LaunchValue { + JLabel label; + JTextField value; + void show(AltosState state, int crc_errors) {} + + void show() { + label.setVisible(true); + value.setVisible(true); + } + + void hide() { + label.setVisible(false); + value.setVisible(false); + } + + public void set_font() { + label.setFont(Altos.label_font); + value.setFont(Altos.value_font); + } + + void reset() { + value.setText(""); + } + public LaunchValue (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + } + } + + class Battery extends LaunchStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.2f V", state.battery)); + lights.set(state.battery > 3.7); + } + public Battery (GridBagLayout layout, int y) { + super(layout, y, "Battery Voltage"); + } + } + + Battery battery; + + class Apogee extends LaunchStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.drogue_sense)); + lights.set(state.drogue_sense > 3.2); + } + public Apogee (GridBagLayout layout, int y) { + super(layout, y, "Apogee Igniter Voltage"); + } + } + + Apogee apogee; + + class Main extends LaunchStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4.2f V", state.main_sense)); + lights.set(state.main_sense > 3.2); + } + public Main (GridBagLayout layout, int y) { + super(layout, y, "Main Igniter Voltage"); + } + } + + Main main; + + class LoggingReady extends LaunchStatus { + void show (AltosState state, int crc_errors) { + show(); + if (state.data.flight != 0) { + if (state.data.state <= Altos.ao_flight_pad) + value.setText("Ready to record"); + else if (state.data.state < Altos.ao_flight_landed) + value.setText("Recording data"); + else + value.setText("Recorded data"); + } + else + value.setText("Storage full"); + lights.set(state.data.flight != 0); + } + public LoggingReady (GridBagLayout layout, int y) { + super(layout, y, "On-board Data Logging"); + } + } + + LoggingReady logging_ready; + + class GPSLocked extends LaunchStatus { + void show (AltosState state, int crc_errors) { + show(); + value.setText(String.format("%4d sats", state.gps.nsat)); + lights.set(state.gps.locked && state.gps.nsat >= 4); + } + public GPSLocked (GridBagLayout layout, int y) { + super (layout, y, "GPS Locked"); + } + } + + GPSLocked gps_locked; + + class GPSReady extends LaunchStatus { + void show (AltosState state, int crc_errors) { + show(); + if (state.gps_ready) + value.setText("Ready"); + else + value.setText(String.format("Waiting %d", state.gps_waiting)); + lights.set(state.gps_ready); + } + public GPSReady (GridBagLayout layout, int y) { + super (layout, y, "GPS Ready"); + } + } + + GPSReady gps_ready; + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %4d° %9.6f", h, deg, min); + } + + class PadLat extends LaunchValue { + void show (AltosState state, int crc_errors) { + show(); + value.setText(pos(state.pad_lat,"N", "S")); + } + public PadLat (GridBagLayout layout, int y) { + super (layout, y, "Pad Latitude"); + } + } + + PadLat pad_lat; + + class PadLon extends LaunchValue { + void show (AltosState state, int crc_errors) { + show(); + value.setText(pos(state.pad_lon,"E", "W")); + } + public PadLon (GridBagLayout layout, int y) { + super (layout, y, "Pad Longitude"); + } + } + + PadLon pad_lon; + + class PadAlt extends LaunchValue { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.0f m", state.pad_alt)); + } + public PadAlt (GridBagLayout layout, int y) { + super (layout, y, "Pad Altitude"); + } + } + + PadAlt pad_alt; + + public void reset() { + battery.reset(); + apogee.reset(); + main.reset(); + logging_ready.reset(); + gps_locked.reset(); + gps_ready.reset(); + pad_lat.reset(); + pad_lon.reset(); + pad_alt.reset(); + } + + public void set_font() { + battery.set_font(); + apogee.set_font(); + main.set_font(); + logging_ready.set_font(); + gps_locked.set_font(); + gps_ready.set_font(); + pad_lat.set_font(); + pad_lon.set_font(); + pad_alt.set_font(); + } + + public void show(AltosState state, int crc_errors) { + battery.show(state, crc_errors); + if (state.drogue_sense == AltosRecord.MISSING) + apogee.hide(); + else + apogee.show(state, crc_errors); + if (state.main_sense == AltosRecord.MISSING) + main.hide(); + else + main.show(state, crc_errors); + logging_ready.show(state, crc_errors); + pad_alt.show(state, crc_errors); + if (state.gps != null && state.gps.connected) { + gps_locked.show(state, crc_errors); + gps_ready.show(state, crc_errors); + pad_lat.show(state, crc_errors); + pad_lon.show(state, crc_errors); + } else { + gps_locked.hide(); + gps_ready.hide(); + pad_lat.hide(); + pad_lon.hide(); + } + } + + public AltosPad() { + layout = new GridBagLayout(); + + setLayout(layout); + + /* Elements in pad display: + * + * Battery voltage + * Igniter continuity + * GPS lock status + * GPS ready status + * GPS location + * Pad altitude + * RSSI + */ + battery = new Battery(layout, 0); + apogee = new Apogee(layout, 1); + main = new Main(layout, 2); + logging_ready = new LoggingReady(layout, 3); + gps_locked = new GPSLocked(layout, 4); + gps_ready = new GPSReady(layout, 5); + pad_lat = new PadLat(layout, 6); + pad_lon = new PadLon(layout, 7); + pad_alt = new PadAlt(layout, 8); + } +} diff --git a/altosui/AltosRomconfig.java b/altosui/AltosRomconfig.java new file mode 100644 index 00000000..0a283e51 --- /dev/null +++ b/altosui/AltosRomconfig.java @@ -0,0 +1,148 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.io.*; +import org.altusmetrum.AltosLib.*; + +public class AltosRomconfig { + public boolean valid; + public int version; + public int check; + public int serial_number; + public int radio_calibration; + + static int get_int(byte[] bytes, int start, int len) { + int v = 0; + int o = 0; + while (len > 0) { + v = v | ((((int) bytes[start]) & 0xff) << o); + start++; + len--; + o += 8; + } + return v; + } + + static void put_int(int value, byte[] bytes, int start, int len) { + while (len > 0) { + bytes[start] = (byte) (value & 0xff); + start++; + len--; + value >>= 8; + } + } + + static void put_string(String value, byte[] bytes, int start) { + for (int i = 0; i < value.length(); i++) + bytes[start + i] = (byte) value.charAt(i); + } + + static final int AO_USB_DESC_STRING = 3; + + static void put_usb_serial(int value, byte[] bytes, int start) { + int offset = start + 0xa; + int string_num = 0; + + while (offset < bytes.length && bytes[offset] != 0) { + if (bytes[offset + 1] == AO_USB_DESC_STRING) { + ++string_num; + if (string_num == 4) + break; + } + offset += ((int) bytes[offset]) & 0xff; + } + if (offset >= bytes.length || bytes[offset] == 0) + return; + int len = ((((int) bytes[offset]) & 0xff) - 2) / 2; + String fmt = String.format("%%0%dd", len); + + String s = String.format(fmt, value); + if (s.length() != len) { + System.out.printf("weird usb length issue %s isn't %d\n", + s, len); + return; + } + for (int i = 0; i < len; i++) { + bytes[offset + 2 + i*2] = (byte) s.charAt(i); + bytes[offset + 2 + i*2+1] = 0; + } + } + + public AltosRomconfig(byte[] bytes, int offset) { + version = get_int(bytes, offset + 0, 2); + check = get_int(bytes, offset + 2, 2); + if (check == (~version & 0xffff)) { + switch (version) { + case 2: + case 1: + serial_number = get_int(bytes, offset + 4, 2); + radio_calibration = get_int(bytes, offset + 6, 4); + valid = true; + break; + } + } + } + + public AltosRomconfig(AltosHexfile hexfile) { + this(hexfile.data, 0xa0 - hexfile.address); + } + + public void write(byte[] bytes, int offset) throws IOException { + if (!valid) + throw new IOException("rom configuration invalid"); + + if (offset < 0 || bytes.length < offset + 10) + throw new IOException("image cannot contain rom config"); + + AltosRomconfig existing = new AltosRomconfig(bytes, offset); + if (!existing.valid) + throw new IOException("image does not contain existing rom config"); + + switch (existing.version) { + case 2: + put_usb_serial(serial_number, bytes, offset); + case 1: + put_int(serial_number, bytes, offset + 4, 2); + put_int(radio_calibration, bytes, offset + 6, 4); + break; + } + } + + public void write (AltosHexfile hexfile) throws IOException { + write(hexfile.data, 0xa0 - hexfile.address); + AltosRomconfig check = new AltosRomconfig(hexfile); + if (!check.valid()) + throw new IOException("writing new rom config failed\n"); + } + + public AltosRomconfig(int in_serial_number, int in_radio_calibration) { + valid = true; + version = 1; + check = (~version & 0xffff); + serial_number = in_serial_number; + radio_calibration = in_radio_calibration; + } + + public boolean valid() { + return valid && serial_number != 0; + } + + public AltosRomconfig() { + valid = false; + } +} diff --git a/altosui/AltosRomconfigUI.java b/altosui/AltosRomconfigUI.java new file mode 100644 index 00000000..306b8623 --- /dev/null +++ b/altosui/AltosRomconfigUI.java @@ -0,0 +1,197 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import org.altusmetrum.AltosLib.*; + +public class AltosRomconfigUI + extends AltosDialog + implements ActionListener +{ + Container pane; + Box box; + JLabel serial_label; + JLabel radio_calibration_label; + + JFrame owner; + JTextField serial_value; + JTextField radio_calibration_value; + + JButton ok; + JButton cancel; + + /* Build the UI using a grid bag */ + public AltosRomconfigUI(JFrame in_owner) { + super (in_owner, "Configure TeleMetrum Rom Values", true); + + owner = in_owner; + GridBagConstraints c; + + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 3; c.gridy = 0; + c.gridwidth = 3; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + serial_value = new JTextField("0"); + pane.add(serial_value, c); + + /* Radio calibration value */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 1; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("Radio Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 3; c.gridy = 1; + c.gridwidth = 3; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JTextField("1186611"); + pane.add(radio_calibration_value, c); + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 2; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + ok = new JButton("OK"); + pane.add(ok, c); + ok.addActionListener(this); + ok.setActionCommand("ok"); + + c = new GridBagConstraints(); + c.gridx = 3; c.gridy = 2; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + cancel = new JButton("Cancel"); + pane.add(cancel, c); + cancel.addActionListener(this); + cancel.setActionCommand("cancel"); + + pack(); + setLocationRelativeTo(owner); + } + + public AltosRomconfigUI(JFrame frame, AltosRomconfig config) { + this(frame); + set(config); + } + + boolean selected; + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("ok")) { + AltosRomconfig romconfig = romconfig(); + if (romconfig == null || !romconfig.valid()) { + JOptionPane.showMessageDialog(this, + "Invalid serial number or radio calibration value", + "Invalid rom configuration", + JOptionPane.ERROR_MESSAGE); + return; + } + selected = true; + } + setVisible(false); + } + + int serial() { + return Integer.parseInt(serial_value.getText()); + } + + void set_serial(int serial) { + serial_value.setText(String.format("%d", serial)); + } + + int radio_calibration() { + return Integer.parseInt(radio_calibration_value.getText()); + } + + void set_radio_calibration(int calibration) { + radio_calibration_value.setText(String.format("%d", calibration)); + } + + public void set(AltosRomconfig config) { + if (config != null && config.valid()) { + set_serial(config.serial_number); + set_radio_calibration(config.radio_calibration); + } + } + + AltosRomconfig romconfig() { + try { + return new AltosRomconfig(serial(), radio_calibration()); + } catch (NumberFormatException ne) { + return null; + } + } + + public AltosRomconfig showDialog() { + setVisible(true); + if (selected) + return romconfig(); + return null; + } + + public static AltosRomconfig show(JFrame frame, AltosRomconfig config) { + AltosRomconfigUI ui = new AltosRomconfigUI(frame, config); + return ui.showDialog(); + } +} diff --git a/altosui/AltosScanUI.java b/altosui/AltosScanUI.java new file mode 100644 index 00000000..9da1290f --- /dev/null +++ b/altosui/AltosScanUI.java @@ -0,0 +1,553 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +class AltosScanResult { + String callsign; + int serial; + int flight; + AltosFrequency frequency; + int telemetry; + + boolean interrupted = false; + + public String toString() { + return String.format("%-9.9s serial %-4d flight %-4d (%s %s)", + callsign, serial, flight, frequency.toShortString(), Altos.telemetry_name(telemetry)); + } + + public String toShortString() { + return String.format("%s %d %d %7.3f %d", + callsign, serial, flight, frequency, telemetry); + } + + public AltosScanResult(String in_callsign, int in_serial, + int in_flight, AltosFrequency in_frequency, int in_telemetry) { + callsign = in_callsign; + serial = in_serial; + flight = in_flight; + frequency = in_frequency; + telemetry = in_telemetry; + } + + public boolean equals(AltosScanResult other) { + return (serial == other.serial && + frequency.frequency == other.frequency.frequency && + telemetry == other.telemetry); + } + + public boolean up_to_date(AltosScanResult other) { + if (flight == 0 && other.flight != 0) { + flight = other.flight; + return false; + } + if (callsign.equals("N0CALL") && !other.callsign.equals("N0CALL")) { + callsign = other.callsign; + return false; + } + return true; + } +} + +class AltosScanResults extends LinkedList<AltosScanResult> implements ListModel { + + LinkedList<ListDataListener> listeners = new LinkedList<ListDataListener>(); + + void changed(ListDataEvent de) { + for (ListDataListener l : listeners) + l.contentsChanged(de); + } + + public boolean add(AltosScanResult r) { + int i = 0; + for (AltosScanResult old : this) { + if (old.equals(r)) { + if (!old.up_to_date(r)) + changed (new ListDataEvent(this, + ListDataEvent.CONTENTS_CHANGED, + i, i)); + return true; + } + i++; + } + + super.add(r); + changed(new ListDataEvent(this, + ListDataEvent.INTERVAL_ADDED, + this.size() - 2, this.size() - 1)); + return true; + } + + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + + public AltosScanResult getElementAt(int i) { + return this.get(i); + } + + public int getSize() { + return this.size(); + } +} + +public class AltosScanUI + extends AltosDialog + implements ActionListener +{ + AltosUI owner; + AltosDevice device; + AltosConfigData config_data; + AltosTelemetryReader reader; + private JList list; + private JLabel scanning_label; + private JLabel frequency_label; + private JLabel telemetry_label; + private JButton cancel_button; + private JButton monitor_button; + private JCheckBox[] telemetry_boxes; + javax.swing.Timer timer; + AltosScanResults results = new AltosScanResults(); + + int telemetry; + + final static int timeout = 1200; + TelemetryHandler handler; + Thread thread; + AltosFrequency[] frequencies; + int frequency_index; + + void scan_exception(Exception e) { + if (e instanceof FileNotFoundException) { + JOptionPane.showMessageDialog(owner, + ((FileNotFoundException) e).getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof AltosSerialInUseException) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } else if (e instanceof IOException) { + IOException ee = (IOException) e; + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } else { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + close(); + } + + class TelemetryHandler implements Runnable { + + public void run() { + + boolean interrupted = false; + + try { + for (;;) { + try { + AltosRecord record = reader.read(); + if (record == null) + continue; + if ((record.seen & AltosRecord.seen_flight) != 0) { + final AltosScanResult result = new AltosScanResult(record.callsign, + record.serial, + record.flight, + frequencies[frequency_index], + telemetry); + Runnable r = new Runnable() { + public void run() { + results.add(result); + } + }; + SwingUtilities.invokeLater(r); + } + } catch (ParseException pp) { + } catch (AltosCRCException ce) { + } + } + } catch (InterruptedException ee) { + interrupted = true; + } catch (IOException ie) { + } finally { + reader.close(interrupted); + } + } + } + + void set_label() { + frequency_label.setText(String.format("Frequency: %s", frequencies[frequency_index].toString())); + telemetry_label.setText(String.format("Telemetry: %s", Altos.telemetry_name(telemetry))); + } + + void set_telemetry() { + reader.set_telemetry(telemetry); + } + + void set_frequency() throws InterruptedException, TimeoutException { + reader.set_frequency(frequencies[frequency_index].frequency); + reader.reset(); + } + + void next() throws InterruptedException, TimeoutException { + reader.set_monitor(false); + Thread.sleep(100); + ++frequency_index; + if (frequency_index >= frequencies.length || + !telemetry_boxes[telemetry - Altos.ao_telemetry_min].isSelected()) + { + frequency_index = 0; + do { + ++telemetry; + if (telemetry > Altos.ao_telemetry_max) + telemetry = Altos.ao_telemetry_min; + } while (!telemetry_boxes[telemetry - Altos.ao_telemetry_min].isSelected()); + set_telemetry(); + } + set_frequency(); + set_label(); + reader.set_monitor(true); + } + + + void close() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + if (timer != null) + timer.stop(); + setVisible(false); + dispose(); + } + + void tick_timer() throws InterruptedException, TimeoutException { + next(); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + try { + if (cmd.equals("cancel")) + close(); + + if (cmd.equals("tick")) + tick_timer(); + + if (cmd.equals("telemetry")) { + int k; + int scanning_telemetry = 0; + for (k = Altos.ao_telemetry_min; k <= Altos.ao_telemetry_max; k++) { + int j = k - Altos.ao_telemetry_min; + if (telemetry_boxes[j].isSelected()) + scanning_telemetry |= (1 << k); + } + if (scanning_telemetry == 0) { + scanning_telemetry |= (1 << Altos.ao_telemetry_standard); + telemetry_boxes[Altos.ao_telemetry_standard - Altos.ao_telemetry_min].setSelected(true); + } + AltosUIPreferences.set_scanning_telemetry(scanning_telemetry); + } + + if (cmd.equals("monitor")) { + close(); + AltosScanResult r = (AltosScanResult) (list.getSelectedValue()); + if (r != null) { + if (device != null) { + if (reader != null) { + reader.set_telemetry(r.telemetry); + reader.set_frequency(r.frequency.frequency); + reader.save_frequency(); + owner.telemetry_window(device); + } + } + } + } + } catch (TimeoutException te) { + close(); + } catch (InterruptedException ie) { + close(); + } + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosScanUI ui; + + public ConfigListener(AltosScanUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "close")); + } + } + + private boolean open() { + device = AltosDeviceDialog.show(owner, Altos.product_basestation); + if (device == null) + return false; + try { + reader = new AltosTelemetryReader(new AltosSerial(device)); + set_frequency(); + set_telemetry(); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + } + reader.flush(); + handler = new TelemetryHandler(); + thread = new Thread(handler); + thread.start(); + return true; + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + "Unkonwn I/O error", + JOptionPane.ERROR_MESSAGE); + } catch (TimeoutException te) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + "Timeout error", + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + "Interrupted exception", + JOptionPane.ERROR_MESSAGE); + } + if (reader != null) + reader.close(false); + return false; + } + + public AltosScanUI(AltosUI in_owner) { + + owner = in_owner; + + frequencies = AltosUIPreferences.common_frequencies(); + frequency_index = 0; + telemetry = Altos.ao_telemetry_min; + + if (!open()) + return; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + timer = new javax.swing.Timer(timeout, this); + timer.setActionCommand("tick"); + timer.restart(); + + owner = in_owner; + + pane.setLayout(new GridBagLayout()); + + scanning_label = new JLabel("Scanning:"); + frequency_label = new JLabel(""); + telemetry_label = new JLabel(""); + + set_label(); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.WEST; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + + pane.add(scanning_label, c); + c.gridy = 1; + pane.add(frequency_label, c); + c.gridy = 2; + pane.add(telemetry_label, c); + + int scanning_telemetry = AltosUIPreferences.scanning_telemetry(); + telemetry_boxes = new JCheckBox[Altos.ao_telemetry_max - Altos.ao_telemetry_min + 1]; + for (int k = Altos.ao_telemetry_min; k <= Altos.ao_telemetry_max; k++) { + int j = k - Altos.ao_telemetry_min; + telemetry_boxes[j] = new JCheckBox(AltosLib.telemetry_name(k)); + c.gridy = 3 + j; + pane.add(telemetry_boxes[j], c); + telemetry_boxes[j].setActionCommand("telemetry"); + telemetry_boxes[j].addActionListener(this); + telemetry_boxes[j].setSelected((scanning_telemetry & (1 << k)) != 0); + } + + int y_offset = 3 + (Altos.ao_telemetry_max - Altos.ao_telemetry_min + 1); + + list = new JList(results) { + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + }; + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + monitor_button.doClick(); //emulate button click + } + } + }); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(400, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to bottom. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); + + JLabel label = new JLabel("Select Device"); + label.setLabelFor(list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = y_offset; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(listPane, c); + + cancel_button = new JButton("Cancel"); + cancel_button.addActionListener(this); + cancel_button.setActionCommand("cancel"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = y_offset + 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(cancel_button, c); + + monitor_button = new JButton("Monitor"); + monitor_button.addActionListener(this); + monitor_button.setActionCommand("monitor"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 1; + c.gridy = y_offset + 1; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(monitor_button, c); + + pack(); + setLocationRelativeTo(owner); + + addWindowListener(new ConfigListener(this)); + + setVisible(true); + } +}
\ No newline at end of file diff --git a/altosui/AltosSerial.java b/altosui/AltosSerial.java new file mode 100644 index 00000000..6cee1609 --- /dev/null +++ b/altosui/AltosSerial.java @@ -0,0 +1,209 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +/* + * Deal with TeleDongle on a serial port + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; +import java.text.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +/* + * This class reads from the serial port and places each received + * line in a queue. Dealing with that queue is left up to other + * threads. + */ + +public class AltosSerial extends AltosLink { + + static java.util.List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>()); + + AltosDevice device; + SWIGTYPE_p_altos_file altos; + Thread input_thread; + String line; + byte[] line_bytes; + int line_count; + Frame frame; + + public int getchar() { + if (altos == null) + return ERROR; + return libaltos.altos_getchar(altos, 0); + } + + public void flush_output() { + super.flush_output(); + if (altos != null) { + if (libaltos.altos_flush(altos) != 0) { + libaltos.altos_close(altos); + altos = null; + abort_reply(); + } + } + } + + JDialog timeout_dialog; + + private void start_timeout_dialog_internal() { + + Object[] options = { "Cancel" }; + + JOptionPane pane = new JOptionPane(); + pane.setMessage(String.format("Connecting to %s, %7.3f MHz", device.toShortString(), frequency)); + pane.setOptions(options); + pane.setInitialValue(null); + + timeout_dialog = pane.createDialog(frame, "Connecting..."); + + timeout_dialog.setVisible(true); + + Object o = pane.getValue(); + if (o == null) + return; + if (options[0].equals(o)) + reply_abort = true; + timeout_dialog.dispose(); + timeout_dialog = null; + } + + /* + * These are required by the AltosLink implementation + */ + + public boolean can_cancel_reply() { + /* + * Can cancel any replies not called from the dispatch thread + */ + return !SwingUtilities.isEventDispatchThread(); + } + + public boolean show_reply_timeout() { + if (!SwingUtilities.isEventDispatchThread() && frame != null) { + Runnable r = new Runnable() { + public void run() { + start_timeout_dialog_internal(); + } + }; + SwingUtilities.invokeLater(r); + return true; + } + return false; + } + + public void hide_reply_timeout() { + Runnable r = new Runnable() { + public void run() { + timeout_dialog.setVisible(false); + } + }; + SwingUtilities.invokeLater(r); + } + + public void close() { + if (remote) { + try { + stop_remote(); + } catch (InterruptedException ie) { + } + } + if (in_reply != 0) + System.out.printf("Uh-oh. Closing active serial device\n"); + + if (altos != null) { + libaltos.altos_close(altos); + } + if (input_thread != null) { + try { + input_thread.interrupt(); + input_thread.join(); + } catch (InterruptedException e) { + } + input_thread = null; + } + if (altos != null) { + libaltos.altos_free(altos); + altos = null; + } + synchronized (devices_opened) { + devices_opened.remove(device.getPath()); + } + if (debug) + System.out.printf("Closing %s\n", device.getPath()); + } + + private void putc(char c) { + if (altos != null) + if (libaltos.altos_putchar(altos, c) != 0) { + libaltos.altos_close(altos); + altos = null; + abort_reply(); + } + } + + public void print(String data) { + for (int i = 0; i < data.length(); i++) + putc(data.charAt(i)); + } + + private void open() throws FileNotFoundException, AltosSerialInUseException { + synchronized (devices_opened) { + if (devices_opened.contains(device.getPath())) + throw new AltosSerialInUseException(device); + devices_opened.add(device.getPath()); + } + altos = device.open(); + if (altos == null) { + final String message = device.getErrorString(); + close(); + throw new FileNotFoundException(String.format("%s (%s)", + device.toShortString(), message)); + } + if (debug) + System.out.printf("Open %s\n", device.getPath()); + input_thread = new Thread(this); + input_thread.start(); + print("~\nE 0\n"); + set_monitor(false); + flush_output(); + } + + public void set_frame(Frame in_frame) { + frame = in_frame; + } + + public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + device = in_device; + frame = null; + serial = device.getSerial(); + name = device.toShortString(); + open(); + } +} diff --git a/altosui/AltosSerialInUseException.java b/altosui/AltosSerialInUseException.java new file mode 100644 index 00000000..7380f331 --- /dev/null +++ b/altosui/AltosSerialInUseException.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public class AltosSerialInUseException extends Exception { + public AltosDevice device; + + public AltosSerialInUseException (AltosDevice in_device) { + device = in_device; + } +} diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java new file mode 100644 index 00000000..b57edcab --- /dev/null +++ b/altosui/AltosSiteMap.java @@ -0,0 +1,447 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { + // preferred vertical step in a tile in naut. miles + // will actually choose a step size between x and 2x, where this + // is 1.5x + static final double tile_size_nmi = 0.75; + + static final int px_size = 512; + + static final int MAX_TILE_DELTA = 100; + + private static Point2D.Double translatePoint(Point2D.Double p, + Point2D.Double d) + { + return new Point2D.Double(p.x + d.x, p.y + d.y); + } + + static class LatLng { + public double lat, lng; + public LatLng(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + } + + // based on google js + // http://maps.gstatic.com/intl/en_us/mapfiles/api-3/2/10/main.js + // search for fromLatLngToPoint and fromPointToLatLng + private static Point2D.Double pt(LatLng latlng, int zoom) { + double scale_x = 256/360.0 * Math.pow(2, zoom); + double scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); + return pt(latlng, scale_x, scale_y); + } + + private static Point2D.Double pt(LatLng latlng, + double scale_x, double scale_y) + { + Point2D.Double res = new Point2D.Double(); + double e; + + res.x = latlng.lng * scale_x; + + e = Math.sin(Math.toRadians(latlng.lat)); + e = Math.max(e,-(1-1.0E-15)); + e = Math.min(e, 1-1.0E-15 ); + + res.y = 0.5*Math.log((1+e)/(1-e))*-scale_y; + return res; + } + + static private LatLng latlng(Point2D.Double pt, + double scale_x, double scale_y) + { + double lat, lng; + double rads; + + lng = pt.x/scale_x; + rads = 2 * Math.atan(Math.exp(-pt.y/scale_y)); + lat = Math.toDegrees(rads - Math.PI/2); + + return new LatLng(lat,lng); + } + + int zoom; + double scale_x, scale_y; + + int radius; /* half width/height of tiles to load */ + + private Point2D.Double pt(double lat, double lng) { + return pt(new LatLng(lat, lng), scale_x, scale_y); + } + + private LatLng latlng(double x, double y) { + return latlng(new Point2D.Double(x,y), scale_x, scale_y); + } + private LatLng latlng(Point2D.Double pt) { + return latlng(pt, scale_x, scale_y); + } + + ConcurrentHashMap<Point,AltosSiteMapTile> mapTiles = new ConcurrentHashMap<Point,AltosSiteMapTile>(); + Point2D.Double centre; + + private Point2D.Double tileCoordOffset(Point p) { + return new Point2D.Double(centre.x - p.x*px_size, + centre.y - p.y * px_size); + } + + private Point tileOffset(Point2D.Double p) { + return new Point((int)Math.floor((centre.x+p.x)/px_size), + (int)Math.floor((centre.y+p.y)/px_size)); + } + + private Point2D.Double getBaseLocation(double lat, double lng) { + Point2D.Double locn, north_step; + + zoom = 2; + // stupid loop structure to please Java's control flow analysis + do { + zoom++; + scale_x = 256/360.0 * Math.pow(2, zoom); + scale_y = 256/(2.0*Math.PI) * Math.pow(2, zoom); + locn = pt(lat, lng); + north_step = pt(lat+tile_size_nmi*4/3/60.0, lng); + if (locn.y - north_step.y > px_size) + break; + } while (zoom < 22); + locn.x = -px_size * Math.floor(locn.x/px_size); + locn.y = -px_size * Math.floor(locn.y/px_size); + return locn; + } + + public void reset() { + // nothing + } + + public void set_font() { + // nothing + } + + private void loadMap(final AltosSiteMapTile tile, + File pngfile, String pngurl) + { + final ImageIcon res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); + if (res != null) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + tile.loadMap(res); + } + }); + } else { + System.out.printf("# Failed to fetch file %s\n", pngfile); + System.out.printf(" wget -O '%s' '%s'\n", pngfile, pngurl); + } + } + + File pngfile; + String pngurl; + + public int prefetchMap(int x, int y) { + LatLng map_latlng = latlng( + -centre.x + x*px_size + px_size/2, + -centre.y + y*px_size + px_size/2); + pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + if (pngfile.exists()) { + return 1; + } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { + return 0; + } else { + return -1; + } + } + + public static void prefetchMaps(double lat, double lng, int w, int h) { + AltosSiteMap asm = new AltosSiteMap(true); + asm.centre = asm.getBaseLocation(lat, lng); + + Point2D.Double p = new Point2D.Double(); + Point2D.Double p2; + int dx = -w/2, dy = -h/2; + for (int y = dy; y < h+dy; y++) { + for (int x = dx; x < w+dx; x++) { + int r = asm.prefetchMap(x, y); + switch (r) { + case 1: + System.out.printf("Already have %s\n", asm.pngfile); + break; + case 0: + System.out.printf("Fetched map %s\n", asm.pngfile); + break; + case -1: + System.out.printf("# Failed to fetch file %s\n", asm.pngfile); + System.out.printf(" wget -O '%s' ''\n", asm.pngfile, asm.pngurl); + break; + } + } + } + } + + public String initMap(Point offset) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double coord = tileCoordOffset(offset); + + LatLng map_latlng = latlng(px_size/2-coord.x, px_size/2-coord.y); + + File pngfile = MapFile(map_latlng.lat, map_latlng.lng, zoom); + String pngurl = MapURL(map_latlng.lat, map_latlng.lng, zoom); + loadMap(tile, pngfile, pngurl); + return pngfile.toString(); + } + + public void setBaseLocation(double lat, double lng) { + for (Point k : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(k); + tile.clearMap(); + } + + centre = getBaseLocation(lat, lng); + scrollRocketToVisible(pt(lat,lng)); + } + + private void initMaps(double lat, double lng) { + setBaseLocation(lat, lng); + + Thread thread = new Thread() { + public void run() { + for (Point k : mapTiles.keySet()) + initMap(k); + } + }; + thread.start(); + } + + private static File MapFile(double lat, double lng, int zoom) { + char chlat = lat < 0 ? 'S' : 'N'; + char chlng = lng < 0 ? 'W' : 'E'; + if (lat < 0) lat = -lat; + if (lng < 0) lng = -lng; + return new File(AltosUIPreferences.mapdir(), + String.format("map-%c%.6f,%c%.6f-%d.png", + chlat, lat, chlng, lng, zoom)); + } + + private static String MapURL(double lat, double lng, int zoom) { + return String.format("http://maps.google.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&sensor=false&maptype=hybrid&format=png32", lat, lng, zoom, px_size, px_size); + } + + boolean initialised = false; + Point2D.Double last_pt = null; + int last_state = -1; + + public void show(double lat, double lon) { + initMaps(lat, lon); + scrollRocketToVisible(pt(lat, lon)); + } + public void show(final AltosState state, final int crc_errors) { + // if insufficient gps data, nothing to update + if (!state.gps.locked && state.gps.nsat < 4) + return; + + if (!initialised) { + if (state.pad_lat != 0 || state.pad_lon != 0) { + initMaps(state.pad_lat, state.pad_lon); + initialised = true; + } else if (state.gps.lat != 0 || state.gps.lon != 0) { + initMaps(state.gps.lat, state.gps.lon); + initialised = true; + } else { + return; + } + } + + final Point2D.Double pt = pt(state.gps.lat, state.gps.lon); + if (last_pt == pt && last_state == state.state) + return; + + if (last_pt == null) { + last_pt = pt; + } + boolean in_any = false; + for (Point offset : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double ref, lref; + ref = translatePoint(pt, tileCoordOffset(offset)); + lref = translatePoint(last_pt, tileCoordOffset(offset)); + tile.show(state, crc_errors, lref, ref); + if (0 <= ref.x && ref.x < px_size) + if (0 <= ref.y && ref.y < px_size) + in_any = true; + } + + Point offset = tileOffset(pt); + if (!in_any) { + Point2D.Double ref, lref; + ref = translatePoint(pt, tileCoordOffset(offset)); + lref = translatePoint(last_pt, tileCoordOffset(offset)); + + AltosSiteMapTile tile = createTile(offset); + tile.show(state, crc_errors, lref, ref); + initMap(offset); + finishTileLater(tile, offset); + } + + scrollRocketToVisible(pt); + + if (offset != tileOffset(last_pt)) { + ensureTilesAround(offset); + } + + last_pt = pt; + last_state = state.state; + } + + public void draw_circle(double lat, double lon) { + final Point2D.Double pt = pt(lat, lon); + + for (Point offset : mapTiles.keySet()) { + AltosSiteMapTile tile = mapTiles.get(offset); + Point2D.Double ref = translatePoint(pt, tileCoordOffset(offset)); + tile.draw_circle(ref); + } + } + + private AltosSiteMapTile createTile(Point offset) { + AltosSiteMapTile tile = new AltosSiteMapTile(px_size); + mapTiles.put(offset, tile); + return tile; + } + private void finishTileLater(final AltosSiteMapTile tile, + final Point offset) + { + SwingUtilities.invokeLater( new Runnable() { + public void run() { + addTileAt(tile, offset); + } + } ); + } + + private void ensureTilesAround(Point base_offset) { + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + Point offset = new Point(base_offset.x + x, base_offset.y + y); + if (mapTiles.containsKey(offset)) + continue; + AltosSiteMapTile tile = createTile(offset); + initMap(offset); + finishTileLater(tile, offset); + } + } + } + + private Point topleft = new Point(0,0); + private void scrollRocketToVisible(Point2D.Double pt) { + Rectangle r = comp.getVisibleRect(); + Point2D.Double copt = translatePoint(pt, tileCoordOffset(topleft)); + int dx = (int)copt.x - r.width/2 - r.x; + int dy = (int)copt.y - r.height/2 - r.y; + if (Math.abs(dx) > r.width/4 || Math.abs(dy) > r.height/4) { + r.x += dx; + r.y += dy; + comp.scrollRectToVisible(r); + } + } + + private void addTileAt(AltosSiteMapTile tile, Point offset) { + if (Math.abs(offset.x) >= MAX_TILE_DELTA || + Math.abs(offset.y) >= MAX_TILE_DELTA) + { + System.out.printf("Rocket too far away from pad (tile %d,%d)\n", + offset.x, offset.y); + return; + } + + boolean review = false; + Rectangle r = comp.getVisibleRect(); + if (offset.x < topleft.x) { + r.x += (topleft.x - offset.x) * px_size; + topleft.x = offset.x; + review = true; + } + if (offset.y < topleft.y) { + r.y += (topleft.y - offset.y) * px_size; + topleft.y = offset.y; + review = true; + } + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.BOTH; + // put some space between the map tiles, debugging only + // c.insets = new Insets(5, 5, 5, 5); + + c.gridx = offset.x + MAX_TILE_DELTA; + c.gridy = offset.y + MAX_TILE_DELTA; + layout.setConstraints(tile, c); + + comp.add(tile); + if (review) { + comp.scrollRectToVisible(r); + } + } + + private AltosSiteMap(boolean knowWhatYouAreDoing) { + if (!knowWhatYouAreDoing) { + throw new RuntimeException("Arggh."); + } + } + + JComponent comp = new JComponent() { }; + private GridBagLayout layout = new GridBagLayout(); + + public AltosSiteMap(int in_radius) { + radius = in_radius; + + GrabNDrag scroller = new GrabNDrag(comp); + + comp.setLayout(layout); + + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + Point offset = new Point(x, y); + AltosSiteMapTile t = createTile(offset); + addTileAt(t, offset); + } + } + setViewportView(comp); + setPreferredSize(new Dimension(500,500)); + } + + public AltosSiteMap() { + this(1); + } +} diff --git a/altosui/AltosSiteMapCache.java b/altosui/AltosSiteMapCache.java new file mode 100644 index 00000000..f729a298 --- /dev/null +++ b/altosui/AltosSiteMapCache.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.net.URL; +import java.net.URLConnection; +import org.altusmetrum.AltosLib.*; + +public class AltosSiteMapCache extends JLabel { + public static boolean fetchMap(File file, String url) { + URL u; + + try { + u = new URL(url); + } catch (java.net.MalformedURLException e) { + return false; + } + + byte[] data; + try { + URLConnection uc = u.openConnection(); + int contentLength = uc.getContentLength(); + InputStream in = new BufferedInputStream(uc.getInputStream()); + int bytesRead = 0; + int offset = 0; + data = new byte[contentLength]; + while (offset < contentLength) { + bytesRead = in.read(data, offset, data.length - offset); + if (bytesRead == -1) + break; + offset += bytesRead; + } + in.close(); + + if (offset != contentLength) { + return false; + } + } catch (IOException e) { + return false; + } + + try { + FileOutputStream out = new FileOutputStream(file); + out.write(data); + out.flush(); + out.close(); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + if (file.exists()) { + file.delete(); + } + return false; + } + return true; + } + + public static ImageIcon fetchAndLoadMap(File pngfile, String url) { + if (!pngfile.exists()) { + if (!fetchMap(pngfile, url)) { + return null; + } + } + return loadMap(pngfile, url); + } + + public static ImageIcon loadMap(File pngfile, String url) { + if (!pngfile.exists()) { + return null; + } + + try { + return new ImageIcon(ImageIO.read(pngfile)); + } catch (IOException e) { + System.out.printf("# IO error trying to load %s\n", pngfile); + return null; + } + } +} diff --git a/altosui/AltosSiteMapPreload.java b/altosui/AltosSiteMapPreload.java new file mode 100644 index 00000000..676b0790 --- /dev/null +++ b/altosui/AltosSiteMapPreload.java @@ -0,0 +1,474 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; +import java.net.URL; +import java.net.URLConnection; +import org.altusmetrum.AltosLib.*; + +class AltosMapPos extends Box { + AltosUI owner; + JLabel label; + JComboBox hemi; + JTextField deg; + JLabel deg_label; + JTextField min; + JLabel min_label; + + public void set_value(double new_value) { + double d, m; + int h; + + h = 0; + if (new_value < 0) { + h = 1; + new_value = -new_value; + } + d = Math.floor(new_value); + deg.setText(String.format("%3.0f", d)); + m = (new_value - d) * 60.0; + min.setText(String.format("%7.4f", m)); + hemi.setSelectedIndex(h); + } + + public double get_value() throws NumberFormatException { + int h = hemi.getSelectedIndex(); + String d_t = deg.getText(); + String m_t = min.getText(); + double d, m, v; + try { + d = Double.parseDouble(d_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid degrees \"%s\"", + d_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + try { + if (m_t.equals("")) + m = 0; + else + m = Double.parseDouble(m_t); + } catch (NumberFormatException ne) { + JOptionPane.showMessageDialog(owner, + String.format("Invalid minutes \"%s\"", + m_t), + "Invalid number", + JOptionPane.ERROR_MESSAGE); + throw ne; + } + v = d + m/60.0; + if (h == 1) + v = -v; + return v; + } + + public AltosMapPos(AltosUI in_owner, + String label_value, + String[] hemi_names, + double default_value) { + super(BoxLayout.X_AXIS); + owner = in_owner; + label = new JLabel(label_value); + hemi = new JComboBox(hemi_names); + hemi.setEditable(false); + deg = new JTextField(5); + deg.setMinimumSize(deg.getPreferredSize()); + deg.setHorizontalAlignment(JTextField.RIGHT); + deg_label = new JLabel("°"); + min = new JTextField(9); + min.setMinimumSize(min.getPreferredSize()); + min_label = new JLabel("'"); + set_value(default_value); + add(label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(hemi); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg); + add(Box.createRigidArea(new Dimension(5, 0))); + add(deg_label); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min); + add(Box.createRigidArea(new Dimension(5, 0))); + add(min_label); + } +} + +class AltosSite { + String name; + double latitude; + double longitude; + + public String toString() { + return name; + } + + public AltosSite(String in_name, double in_latitude, double in_longitude) { + name = in_name; + latitude = in_latitude; + longitude = in_longitude; + } + + public AltosSite(String line) throws ParseException { + String[] elements = line.split(":"); + + if (elements.length < 3) + throw new ParseException(String.format("Invalid site line %s", line), 0); + + name = elements[0]; + + try { + latitude = Double.parseDouble(elements[1]); + longitude = Double.parseDouble(elements[2]); + } catch (NumberFormatException ne) { + throw new ParseException(String.format("Invalid site line %s", line), 0); + } + } +} + +class AltosSites extends Thread { + AltosSiteMapPreload preload; + URL url; + LinkedList<AltosSite> sites; + + void notify_complete() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + preload.set_sites(); + } + }); + } + + void add(AltosSite site) { + sites.add(site); + } + + void add(String line) { + try { + add(new AltosSite(line)); + } catch (ParseException pe) { + } + } + + public void run() { + try { + URLConnection uc = url.openConnection(); + int length = uc.getContentLength(); + + InputStreamReader in_stream = new InputStreamReader(uc.getInputStream(), Altos.unicode_set); + BufferedReader in = new BufferedReader(in_stream); + + for (;;) { + String line = in.readLine(); + if (line == null) + break; + add(line); + } + } catch (IOException e) { + } finally { + notify_complete(); + } + } + + public AltosSites(AltosSiteMapPreload in_preload) { + sites = new LinkedList<AltosSite>(); + preload = in_preload; + try { + url = new URL(Altos.launch_sites_url); + } catch (java.net.MalformedURLException e) { + notify_complete(); + } + start(); + } +} + +public class AltosSiteMapPreload extends AltosDialog implements ActionListener, ItemListener { + AltosUI owner; + AltosSiteMap map; + + AltosMapPos lat; + AltosMapPos lon; + + final static int radius = 4; + final static int width = (radius * 2 + 1); + final static int height = (radius * 2 + 1); + + JProgressBar pbar; + + AltosSites sites; + JLabel site_list_label; + JComboBox site_list; + + JToggleButton load_button; + boolean loading; + JButton close_button; + + static final String[] lat_hemi_names = { "N", "S" }; + static final String[] lon_hemi_names = { "E", "W" }; + + class updatePbar implements Runnable { + int n; + String s; + + public updatePbar(int x, int y, String in_s) { + n = (x + radius) + (y + radius) * width + 1; + s = in_s; + } + + public void run() { + pbar.setValue(n); + pbar.setString(s); + if (n < width * height) { + pbar.setValue(n); + pbar.setString(s); + } else { + pbar.setValue(0); + pbar.setString(""); + load_button.setSelected(false); + loading = false; + } + } + } + + class bgLoad extends Thread { + + AltosSiteMap map; + + public bgLoad(AltosSiteMap in_map) { + map = in_map; + } + + public void run() { + for (int y = -map.radius; y <= map.radius; y++) { + for (int x = -map.radius; x <= map.radius; x++) { + String pngfile; + pngfile = map.initMap(new Point(x,y)); + SwingUtilities.invokeLater(new updatePbar(x, y, pngfile)); + } + } + } + } + + public void set_sites() { + int i = 1; + for (AltosSite site : sites.sites) { + site_list.insertItemAt(site, i); + i++; + } + } + + public void itemStateChanged(ItemEvent e) { + int state = e.getStateChange(); + + if (state == ItemEvent.SELECTED) { + Object o = e.getItem(); + if (o instanceof AltosSite) { + AltosSite site = (AltosSite) o; + lat.set_value(site.latitude); + lon.set_value(site.longitude); + } + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("close")) + setVisible(false); + + if (cmd.equals("load")) { + if (!loading) { + try { + final double latitude = lat.get_value(); + final double longitude = lon.get_value(); + map.setBaseLocation(latitude,longitude); + map.draw_circle(latitude,longitude); + loading = true; + bgLoad thread = new bgLoad(map); + thread.start(); + } catch (NumberFormatException ne) { + load_button.setSelected(false); + } + } + } + } + + public AltosSiteMapPreload(AltosUI in_owner) { + owner = in_owner; + + Container pane = getContentPane(); + GridBagConstraints c = new GridBagConstraints(); + Insets i = new Insets(4,4,4,4); + + pane.setLayout(new GridBagLayout()); + + map = new AltosSiteMap(4); + + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 1; + + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.anchor = GridBagConstraints.CENTER; + + pane.add(map, c); + + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum(width * height); + pbar.setValue(0); + pbar.setString(""); + pbar.setStringPainted(true); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 2; + + pane.add(pbar, c); + + site_list_label = new JLabel ("Known Launch Sites:"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + + pane.add(site_list_label, c); + + site_list = new JComboBox(new String[] { "Site List" }); + site_list.addItemListener(this); + + sites = new AltosSites(this); + + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + + pane.add(site_list, c); + + lat = new AltosMapPos(owner, + "Latitude:", + lat_hemi_names, + 37.167833333); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lat, c); + + lon = new AltosMapPos(owner, + "Longitude:", + lon_hemi_names, + -97.73975); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 0; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(lon, c); + + load_button = new JToggleButton("Load Map"); + load_button.addActionListener(this); + load_button.setActionCommand("load"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(load_button, c); + + close_button = new JButton("Close"); + close_button.addActionListener(this); + close_button.setActionCommand("close"); + + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = i; + c.weightx = 1; + c.weighty = 0; + + c.gridx = 1; + c.gridy = 4; + c.gridwidth = 1; + c.anchor = GridBagConstraints.CENTER; + + pane.add(close_button, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } +}
\ No newline at end of file diff --git a/altosui/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java new file mode 100644 index 00000000..34550219 --- /dev/null +++ b/altosui/AltosSiteMapTile.java @@ -0,0 +1,130 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.lang.Math; +import java.awt.geom.Point2D; +import java.awt.geom.Line2D; +import org.altusmetrum.AltosLib.*; + +public class AltosSiteMapTile extends JLayeredPane { + JLabel mapLabel; + JLabel draw; + Graphics2D g2d; + int px_size; + + public void loadMap(ImageIcon icn) { + mapLabel.setIcon(icn); + } + + public void clearMap() { + fillLabel(mapLabel, Color.GRAY, px_size); + g2d = fillLabel(draw, new Color(127,127,127,0), px_size); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + } + + static Color stateColors[] = { + Color.WHITE, // startup + Color.WHITE, // idle + Color.WHITE, // pad + Color.RED, // boost + Color.PINK, // fast + Color.YELLOW, // coast + Color.CYAN, // drogue + Color.BLUE, // main + Color.BLACK // landed + }; + + private boolean drawn_landed_circle = false; + private boolean drawn_boost_circle = false; + public synchronized void show(AltosState state, int crc_errors, + Point2D.Double last_pt, Point2D.Double pt) + { + if (0 <= state.state && state.state < stateColors.length) { + g2d.setColor(stateColors[state.state]); + } + g2d.draw(new Line2D.Double(last_pt, pt)); + + if (state.state == 3 && !drawn_boost_circle) { + drawn_boost_circle = true; + g2d.setColor(Color.RED); + g2d.drawOval((int)last_pt.x-5, (int)last_pt.y-5, 10, 10); + g2d.drawOval((int)last_pt.x-20, (int)last_pt.y-20, 40, 40); + g2d.drawOval((int)last_pt.x-35, (int)last_pt.y-35, 70, 70); + } + if (state.state == 8 && !drawn_landed_circle) { + drawn_landed_circle = true; + g2d.setColor(Color.BLACK); + g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); + g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); + g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); + } + + repaint(); + } + + public void draw_circle(Point2D.Double pt) { + g2d.setColor(Color.RED); + g2d.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10); + g2d.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40); + g2d.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70); + } + + public static Graphics2D fillLabel(JLabel l, Color c, int px_size) { + BufferedImage img = new BufferedImage(px_size, px_size, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + g.setColor(c); + g.fillRect(0, 0, px_size, px_size); + l.setIcon(new ImageIcon(img)); + return g; + } + + public AltosSiteMapTile(int in_px_size) { + px_size = in_px_size; + setPreferredSize(new Dimension(px_size, px_size)); + + mapLabel = new JLabel(); + fillLabel(mapLabel, Color.GRAY, px_size); + mapLabel.setOpaque(true); + mapLabel.setBounds(0, 0, px_size, px_size); + add(mapLabel, new Integer(0)); + + draw = new JLabel(); + g2d = fillLabel(draw, new Color(127, 127, 127, 0), px_size); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + draw.setBounds(0, 0, px_size, px_size); + draw.setOpaque(false); + + add(draw, new Integer(1)); + } +} diff --git a/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub b/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub Binary files differnew file mode 100755 index 00000000..c661d3e1 --- /dev/null +++ b/altosui/AltosUI.app/Contents/MacOS/JavaApplicationStub diff --git a/altosui/AltosUI.app/Contents/PkgInfo b/altosui/AltosUI.app/Contents/PkgInfo new file mode 100644 index 00000000..8a43480f --- /dev/null +++ b/altosui/AltosUI.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPLAM.O diff --git a/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns b/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns Binary files differnew file mode 100644 index 00000000..fe49f362 --- /dev/null +++ b/altosui/AltosUI.app/Contents/Resources/AltosUIIcon.icns diff --git a/altosui/AltosUI.java b/altosui/AltosUI.java new file mode 100644 index 00000000..926d66f0 --- /dev/null +++ b/altosui/AltosUI.java @@ -0,0 +1,603 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; +import org.altusmetrum.AltosLib.*; + +import libaltosJNI.*; + +public class AltosUI extends AltosFrame { + public AltosVoice voice = new AltosVoice(); + + public static boolean load_library(Frame frame) { + if (!Altos.load_library()) { + JOptionPane.showMessageDialog(frame, + String.format("No AltOS library in \"%s\"", + System.getProperty("java.library.path","<undefined>")), + "Cannot load device access library", + JOptionPane.ERROR_MESSAGE); + return false; + } + return true; + } + + void telemetry_window(AltosDevice device) { + try { + AltosFlightReader reader = new AltosTelemetryReader(new AltosSerial(device)); + if (reader != null) + new AltosFlightUI(voice, reader, device.getSerial()); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(AltosUI.this, + ee.getMessage(), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(AltosUI.this, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(AltosUI.this, + device.toShortString(), + "Unkonwn I/O error", + JOptionPane.ERROR_MESSAGE); + } catch (TimeoutException te) { + JOptionPane.showMessageDialog(this, + device.toShortString(), + "Timeout error", + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + JOptionPane.showMessageDialog(this, + device.toShortString(), + "Interrupted exception", + JOptionPane.ERROR_MESSAGE); + } + } + + Container pane; + GridBagLayout gridbag; + + JButton addButton(int x, int y, String label) { + GridBagConstraints c; + JButton b; + + c = new GridBagConstraints(); + c.gridx = x; c.gridy = y; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + b = new JButton(label); + + Dimension ps = b.getPreferredSize(); + + gridbag.setConstraints(b, c); + add(b, c); + return b; + } + + public AltosUI() { + + load_library(null); + + java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg"); + if (imgURL != null) + setIconImage(new ImageIcon(imgURL).getImage()); + + AltosUIPreferences.set_component(this); + + pane = getContentPane(); + gridbag = new GridBagLayout(); + pane.setLayout(gridbag); + + JButton b; + + b = addButton(0, 0, "Monitor Flight"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConnectToDevice(); + } + }); + b.setToolTipText("Connect to TeleDongle and monitor telemetry"); + b = addButton(1, 0, "Save Flight Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SaveFlightData(); + } + }); + b.setToolTipText("Download and/or delete flight data from an altimeter"); + b = addButton(2, 0, "Replay Flight"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Replay(); + } + }); + b.setToolTipText("Watch an old flight in real-time"); + b = addButton(3, 0, "Graph Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + GraphData(); + } + }); + b.setToolTipText("Present flight data in a graph and table of statistics"); + b = addButton(4, 0, "Export Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ExportData(); + } + }); + b.setToolTipText("Convert flight data for a spreadsheet or GoogleEarth"); + b = addButton(0, 1, "Configure Altimeter"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTeleMetrum(); + } + }); + b.setToolTipText("Set flight, storage and communication parameters"); + b = addButton(1, 1, "Configure AltosUI"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureAltosUI(); + } + }); + b.setToolTipText("Global AltosUI settings"); + + b = addButton(2, 1, "Configure Ground Station"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTeleDongle(); + } + }); + + b = addButton(3, 1, "Flash Image"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FlashImage(); + } + }); + b.setToolTipText("Replace the firmware in any AltusMetrum product"); + + b = addButton(4, 1, "Fire Igniter"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FireIgniter(); + } + }); + b.setToolTipText("Remote control of igniters for deployment testing"); + b = addButton(0, 2, "Scan Channels"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ScanChannels(); + } + }); + b.setToolTipText("Find what channel an altimeter is sending telemetry on"); + b = addButton(1, 2, "Load Maps"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + LoadMaps(); + } + }); + b.setToolTipText("Download satellite images for off-line flight monitoring"); + b = addButton(2, 2, "Monitor Idle"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + IdleMonitor(); + } + }); + b.setToolTipText("Check flight readiness of altimeter in idle mode"); + +// b = addButton(3, 2, "Launch Controller"); +// b.addActionListener(new ActionListener() { +// public void actionPerformed(ActionEvent e) { +// LaunchController(); +// } +// }); + + b = addButton(4, 2, "Quit"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + b.setToolTipText("Close all active windows and terminate AltosUI"); + + setTitle("AltOS"); + + pane.doLayout(); + pane.validate(); + + doLayout(); + validate(); + + setVisible(true); + + Insets i = getInsets(); + Dimension ps = rootPane.getPreferredSize(); + ps.width += i.left + i.right; + ps.height += i.top + i.bottom; + setPreferredSize(ps); + setSize(ps); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + } + + private void ConnectToDevice() { + AltosDevice device = AltosDeviceDialog.show(AltosUI.this, + Altos.product_basestation); + + if (device != null) + telemetry_window(device); + } + + void ConfigureCallsign() { + String result; + result = JOptionPane.showInputDialog(AltosUI.this, + "Configure Callsign", + AltosUIPreferences.callsign()); + if (result != null) + AltosUIPreferences.set_callsign(result); + } + + void ConfigureTeleMetrum() { + new AltosConfig(AltosUI.this); + } + + void ConfigureTeleDongle() { + new AltosConfigTD(AltosUI.this); + } + + void FlashImage() { + AltosFlashUI.show(AltosUI.this); + } + + void FireIgniter() { + new AltosIgniteUI(AltosUI.this); + } + + void ScanChannels() { + new AltosScanUI(AltosUI.this); + } + + void LoadMaps() { + new AltosSiteMapPreload(AltosUI.this); + } + + void LaunchController() { + new AltosLaunchUI(AltosUI.this); + } + + /* + * Replay a flight from telemetry data + */ + private void Replay() { + AltosDataChooser chooser = new AltosDataChooser( + AltosUI.this); + + AltosRecordIterable iterable = chooser.runDialog(); + if (iterable != null) { + AltosFlightReader reader = new AltosReplayReader(iterable.iterator(), + chooser.file()); + new AltosFlightUI(voice, reader); + } + } + + /* Connect to TeleMetrum, either directly or through + * a TeleDongle over the packet link + */ + private void SaveFlightData() { + new AltosEepromManage(AltosUI.this); + } + + /* Load a flight log file and write out a CSV file containing + * all of the data in standard units + */ + + private void ExportData() { + AltosDataChooser chooser; + chooser = new AltosDataChooser(this); + AltosRecordIterable record_reader = chooser.runDialog(); + if (record_reader == null) + return; + new AltosCSVUI(AltosUI.this, record_reader, chooser.file()); + } + + /* Load a flight log CSV file and display a pretty graph. + */ + + private void GraphData() { + AltosDataChooser chooser; + chooser = new AltosDataChooser(this); + AltosRecordIterable record_reader = chooser.runDialog(); + if (record_reader == null) + return; + try { + new AltosGraphUI(record_reader, chooser.filename()); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } + + private void ConfigureAltosUI() { + new AltosConfigureUI(AltosUI.this, voice); + } + + private void IdleMonitor() { + try { + new AltosIdleMonitorUI(this); + } catch (Exception e) { + } + } + + static AltosRecordIterable open_logfile(String filename) { + File file = new File (filename); + try { + FileInputStream in; + + in = new FileInputStream(file); + if (filename.endsWith("eeprom")) + return new AltosEepromIterable(in); + else if (filename.endsWith("mega")) + return new AltosEepromMegaIterable(in); + else + return new AltosTelemetryIterable(in); + } catch (FileNotFoundException fe) { + System.out.printf("%s\n", fe.getMessage()); + return null; + } + } + + static AltosWriter open_csv(String filename) { + File file = new File (filename); + try { + return new AltosCSV(file); + } catch (FileNotFoundException fe) { + System.out.printf("%s\n", fe.getMessage()); + return null; + } + } + + static AltosWriter open_kml(String filename) { + File file = new File (filename); + try { + return new AltosKML(file); + } catch (FileNotFoundException fe) { + System.out.printf("%s\n", fe.getMessage()); + return null; + } + } + + static final int process_none = 0; + static final int process_csv = 1; + static final int process_kml = 2; + static final int process_graph = 3; + static final int process_replay = 4; + static final int process_summary = 5; + + static void process_csv(String input) { + AltosRecordIterable iterable = open_logfile(input); + if (iterable == null) + return; + + String output = Altos.replace_extension(input,".csv"); + System.out.printf("Processing \"%s\" to \"%s\"\n", input, output); + if (input.equals(output)) { + System.out.printf("Not processing '%s'\n", input); + } else { + AltosWriter writer = open_csv(output); + if (writer == null) + return; + writer.write(iterable); + writer.close(); + } + } + + static void process_kml(String input) { + AltosRecordIterable iterable = open_logfile(input); + if (iterable == null) + return; + + String output = Altos.replace_extension(input,".kml"); + System.out.printf("Processing \"%s\" to \"%s\"\n", input, output); + if (input.equals(output)) { + System.out.printf("Not processing '%s'\n", input); + } else { + AltosWriter writer = open_kml(output); + if (writer == null) + return; + writer.write(iterable); + writer.close(); + } + } + + static AltosRecordIterable record_iterable(File file) { + FileInputStream in; + try { + in = new FileInputStream(file); + } catch (Exception e) { + System.out.printf("Failed to open file '%s'\n", file); + return null; + } + AltosRecordIterable recs; + AltosReplayReader reader; + if (file.getName().endsWith("eeprom")) { + recs = new AltosEepromIterable(in); + } else { + recs = new AltosTelemetryIterable(in); + } + return recs; + } + + static AltosRecordIterable record_iterable_file(String filename) { + return record_iterable (new File(filename)); + } + + static AltosReplayReader replay_file(String filename) { + AltosRecordIterable recs = record_iterable_file(filename); + if (recs == null) + return null; + return new AltosReplayReader(recs.iterator(), new File(filename)); + } + + static void process_replay(String filename) { + AltosReplayReader reader = replay_file(filename); + AltosFlightUI flight_ui = new AltosFlightUI(new AltosVoice(), reader); + flight_ui.set_exit_on_close(); + } + + static void process_graph(String filename) { + AltosRecordIterable recs = record_iterable_file(filename); + if (recs == null) + return; + try { + new AltosGraphUI(recs, filename); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } + + static void process_summary(String filename) { + AltosRecordIterable iterable = record_iterable_file(filename); + try { + AltosFlightStats stats = new AltosFlightStats(iterable); + if (stats.serial > 0) + System.out.printf("Serial: %5d\n", stats.serial); + if (stats.flight > 0) + System.out.printf("Flight: %5d\n", stats.flight); + if (stats.year > 0) + System.out.printf("Date: %04d-%02d-%02d\n", + stats.year, stats.month, stats.day); + if (stats.hour > 0) + System.out.printf("Time: %02d:%02d:%02d UTC\n", + stats.hour, stats.minute, stats.second); + System.out.printf("Max height: %6.0f m %6.0f ft\n", + stats.max_height, + AltosConvert.meters_to_feet(stats.max_height)); + System.out.printf("Max speed: %6.0f m/s %6.0f ft/s %6.4f Mach\n", + stats.max_speed, + AltosConvert.meters_to_feet(stats.max_speed), + AltosConvert.meters_to_mach(stats.max_speed)); + if (stats.max_acceleration != AltosRecord.MISSING) { + System.out.printf("Max accel: %6.0f m/s² %6.0f ft/s² %6.2f g\n", + stats.max_acceleration, + AltosConvert.meters_to_feet(stats.max_acceleration), + AltosConvert.meters_to_g(stats.max_acceleration)); + } + System.out.printf("Drogue rate: %6.0f m/s %6.0f ft/s\n", + stats.state_baro_speed[Altos.ao_flight_drogue], + AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_drogue])); + System.out.printf("Main rate: %6.0f m/s %6.0f ft/s\n", + stats.state_baro_speed[Altos.ao_flight_main], + AltosConvert.meters_to_feet(stats.state_baro_speed[Altos.ao_flight_main])); + System.out.printf("Flight time: %6.0f s\n", + stats.state_end[Altos.ao_flight_main] - + stats.state_start[Altos.ao_flight_boost]); + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + } + + public static void help(int code) { + System.out.printf("Usage: altosui [OPTION]... [FILE]...\n"); + System.out.printf(" Options:\n"); + System.out.printf(" --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n"); + System.out.printf(" --replay <filename>\t\trelive the glory of past flights \n"); + System.out.printf(" --graph <filename>\t\tgraph a flight\n"); + System.out.printf(" --csv\tgenerate comma separated output for spreadsheets, etc\n"); + System.out.printf(" --kml\tgenerate KML output for use with Google Earth\n"); + System.exit(code); + } + + public static void main(final String[] args) { + load_library(null); + try { + UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel()); + } catch (Exception e) { + } + /* Handle batch-mode */ + if (args.length == 0) { + AltosUI altosui = new AltosUI(); + altosui.setVisible(true); + + java.util.List<AltosDevice> devices = AltosUSBDevice.list(Altos.product_basestation); + for (AltosDevice device : devices) + altosui.telemetry_window(device); + } else { + int process = process_none; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--help")) + help(0); + else if (args[i].equals("--fetchmaps")) { + if (args.length < i + 3) { + help(1); + } else { + double lat = Double.parseDouble(args[i+1]); + double lon = Double.parseDouble(args[i+2]); + AltosSiteMap.prefetchMaps(lat, lon, 5, 5); + i += 2; + } + } else if (args[i].equals("--replay")) + process = process_replay; + else if (args[i].equals("--kml")) + process = process_kml; + else if (args[i].equals("--csv")) + process = process_csv; + else if (args[i].equals("--graph")) + process = process_graph; + else if (args[i].equals("--summary")) + process = process_summary; + else if (args[i].startsWith("--")) + help(1); + else { + switch (process) { + case process_none: + case process_graph: + process_graph(args[i]); + break; + case process_replay: + process_replay(args[i]); + break; + case process_kml: + process_kml(args[i]); + break; + case process_csv: + process_csv(args[i]); + break; + case process_summary: + process_summary(args[i]); + break; + } + } + } + } + } +} diff --git a/altosui/AltosUIListener.java b/altosui/AltosUIListener.java new file mode 100644 index 00000000..7ee62afc --- /dev/null +++ b/altosui/AltosUIListener.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public interface AltosUIListener { + public void ui_changed(String look_and_feel); +} diff --git a/altosui/AltosUIPreferences.java b/altosui/AltosUIPreferences.java new file mode 100644 index 00000000..10ab26c3 --- /dev/null +++ b/altosui/AltosUIPreferences.java @@ -0,0 +1,176 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.awt.Component; +import javax.swing.*; +import javax.swing.filechooser.FileSystemView; +import org.altusmetrum.AltosLib.*; + +public class AltosUIPreferences extends AltosPreferences { + + /* font size preferences name */ + final static String fontSizePreference = "FONT-SIZE"; + + /* Look&Feel preference name */ + final static String lookAndFeelPreference = "LOOK-AND-FEEL"; + + /* UI Component to pop dialogs up */ + static Component component; + + static LinkedList<AltosFontListener> font_listeners; + + static int font_size = Altos.font_size_medium; + + static LinkedList<AltosUIListener> ui_listeners; + + static String look_and_feel = null; + + /* Serial debug */ + static boolean serial_debug; + + public static void init() { + font_listeners = new LinkedList<AltosFontListener>(); + + font_size = preferences.getInt(fontSizePreference, Altos.font_size_medium); + Altos.set_fonts(font_size); + look_and_feel = preferences.get(lookAndFeelPreference, UIManager.getSystemLookAndFeelClassName()); + + ui_listeners = new LinkedList<AltosUIListener>(); + serial_debug = preferences.getBoolean(serialDebugPreference, false); + AltosLink.set_debug(serial_debug); + } + + static { init(); } + + static void set_component(Component in_component) { + component = in_component; + } + + private static boolean check_dir(File dir) { + if (!dir.exists()) { + if (!dir.mkdirs()) { + JOptionPane.showMessageDialog(component, + dir.getName(), + "Cannot create directory", + JOptionPane.ERROR_MESSAGE); + return false; + } + } else if (!dir.isDirectory()) { + JOptionPane.showMessageDialog(component, + dir.getName(), + "Is not a directory", + JOptionPane.ERROR_MESSAGE); + return false; + } + return true; + } + + /* Configure the log directory. This is where all telemetry and eeprom files + * will be written to, and where replay will look for telemetry files + */ + public static void ConfigureLog() { + JFileChooser logdir_chooser = new JFileChooser(logdir.getParentFile()); + + logdir_chooser.setDialogTitle("Configure Data Logging Directory"); + logdir_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + if (logdir_chooser.showDialog(component, "Select Directory") == JFileChooser.APPROVE_OPTION) { + File dir = logdir_chooser.getSelectedFile(); + if (check_dir(dir)) + set_logdir(dir); + } + } + public static int font_size() { + return font_size; + } + + static void set_fonts() { + } + + public static void set_font_size(int new_font_size) { + font_size = new_font_size; + synchronized (preferences) { + preferences.putInt(fontSizePreference, font_size); + flush_preferences(); + Altos.set_fonts(font_size); + for (AltosFontListener l : font_listeners) + l.font_size_changed(font_size); + } + } + + public static void register_font_listener(AltosFontListener l) { + synchronized (preferences) { + font_listeners.add(l); + } + } + + public static void unregister_font_listener(AltosFontListener l) { + synchronized (preferences) { + font_listeners.remove(l); + } + } + + public static void set_look_and_feel(String new_look_and_feel) { + look_and_feel = new_look_and_feel; + try { + UIManager.setLookAndFeel(look_and_feel); + } catch (Exception e) { + } + synchronized(preferences) { + preferences.put(lookAndFeelPreference, look_and_feel); + flush_preferences(); + for (AltosUIListener l : ui_listeners) + l.ui_changed(look_and_feel); + } + } + + public static String look_and_feel() { + return look_and_feel; + } + + public static void register_ui_listener(AltosUIListener l) { + synchronized(preferences) { + ui_listeners.add(l); + } + } + + public static void unregister_ui_listener(AltosUIListener l) { + synchronized (preferences) { + ui_listeners.remove(l); + } + } + public static void set_serial_debug(boolean new_serial_debug) { + serial_debug = new_serial_debug; + AltosLink.set_debug(serial_debug); + synchronized (preferences) { + preferences.putBoolean(serialDebugPreference, serial_debug); + flush_preferences(); + } + } + + public static boolean serial_debug() { + return serial_debug; + } + +}
\ No newline at end of file diff --git a/altosui/AltosUSBDevice.java b/altosui/AltosUSBDevice.java new file mode 100644 index 00000000..ed5f8307 --- /dev/null +++ b/altosui/AltosUSBDevice.java @@ -0,0 +1,112 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; + +public class AltosUSBDevice extends altos_device implements AltosDevice { + + public String toString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%-20.20s %4d %s", + name, getSerial(), getPath()); + } + + public String toShortString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%s %d %s", + name, getSerial(), getPath()); + + } + + public String getErrorString() { + altos_error error = new altos_error(); + + libaltos.altos_get_last_error(error); + return String.format("%s (%d)", error.getString(), error.getCode()); + } + + public SWIGTYPE_p_altos_file open() { + return libaltos.altos_open(this); + } + + private boolean isAltusMetrum() { + if (getVendor() != Altos.vendor_altusmetrum) + return false; + if (getProduct() < Altos.product_altusmetrum_min) + return false; + if (getProduct() > Altos.product_altusmetrum_max) + return false; + return true; + } + + public boolean matchProduct(int want_product) { + + if (!isAltusMetrum()) + return false; + + if (want_product == Altos.product_any) + return true; + + if (want_product == Altos.product_basestation) + return matchProduct(Altos.product_teledongle) || + matchProduct(Altos.product_teleterra) || + matchProduct(Altos.product_telebt) || + matchProduct(Altos.product_megadongle); + + if (want_product == Altos.product_altimeter) + return matchProduct(Altos.product_telemetrum) || + matchProduct(Altos.product_megametrum); + + int have_product = getProduct(); + + if (have_product == Altos.product_altusmetrum) /* old devices match any request */ + return true; + + if (want_product == have_product) + return true; + + return false; + } + + static java.util.List<AltosDevice> list(int product) { + if (!Altos.load_library()) + return null; + + SWIGTYPE_p_altos_list list = libaltos.altos_list_start(); + + ArrayList<AltosDevice> device_list = new ArrayList<AltosDevice>(); + if (list != null) { + for (;;) { + AltosUSBDevice device = new AltosUSBDevice(); + if (libaltos.altos_list_next(list, device) == 0) + break; + if (device.matchProduct(product)) + device_list.add(device); + } + libaltos.altos_list_finish(list); + } + + return device_list; + } +}
\ No newline at end of file diff --git a/altosui/AltosVersion.java.in b/altosui/AltosVersion.java.in new file mode 100644 index 00000000..b0b3c0cf --- /dev/null +++ b/altosui/AltosVersion.java.in @@ -0,0 +1,22 @@ +/* + * Copyright © 2011 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public class AltosVersion { + public final static String version = "@VERSION@"; +} diff --git a/altosui/AltosVoice.java b/altosui/AltosVoice.java new file mode 100644 index 00000000..ab74e0b3 --- /dev/null +++ b/altosui/AltosVoice.java @@ -0,0 +1,95 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import com.sun.speech.freetts.Voice; +import com.sun.speech.freetts.VoiceManager; +import com.sun.speech.freetts.audio.JavaClipAudioPlayer; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosVoice implements Runnable { + VoiceManager voice_manager; + Voice voice; + LinkedBlockingQueue<String> phrases; + Thread thread; + boolean busy; + + final static String voice_name = "kevin16"; + + public void run() { + try { + for (;;) { + String s = phrases.take(); + voice.speak(s); + synchronized(this) { + if (phrases.isEmpty()) { + busy = false; + notifyAll(); + } + } + } + } catch (InterruptedException e) { + } + } + + public synchronized void drain() throws InterruptedException { + while (busy) + wait(); + } + + public void speak_always(String s) { + try { + if (voice != null) { + synchronized(this) { + busy = true; + phrases.put(s); + } + } + } catch (InterruptedException e) { + } + } + + public void speak(String s) { + if (AltosUIPreferences.voice()) + speak_always(s); + } + + public void speak(String format, Object... parameters) { + speak(String.format(format, parameters)); + } + + public AltosVoice () { + busy = false; + voice_manager = VoiceManager.getInstance(); + voice = voice_manager.getVoice(voice_name); + if (voice != null) { + voice.allocate(); + phrases = new LinkedBlockingQueue<String> (); + thread = new Thread(this); + thread.start(); + } else { + System.out.printf("Voice manager failed to open %s\n", voice_name); + Voice[] voices = voice_manager.getVoices(); + System.out.printf("Available voices:\n"); + for (int i = 0; i < voices.length; i++) { + System.out.println(" " + voices[i].getName() + + " (" + voices[i].getDomain() + " domain)"); + } + } + } +} diff --git a/altosui/AltosWriter.java b/altosui/AltosWriter.java new file mode 100644 index 00000000..b7375204 --- /dev/null +++ b/altosui/AltosWriter.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + + +public interface AltosWriter { + + public void write(AltosRecord record); + + public void write(AltosRecordIterable iterable); + + public void close(); +} diff --git a/altosui/GrabNDrag.java b/altosui/GrabNDrag.java new file mode 100644 index 00000000..c350efec --- /dev/null +++ b/altosui/GrabNDrag.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2010 Anthony Towns <aj@erisian.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.imageio.ImageIO; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import org.altusmetrum.AltosLib.*; + +class GrabNDrag extends MouseInputAdapter { + private JComponent scroll; + private Point startPt = new Point(); + + public GrabNDrag(JComponent scroll) { + this.scroll = scroll; + scroll.addMouseMotionListener(this); + scroll.addMouseListener(this); + scroll.setAutoscrolls(true); + } + + public void mousePressed(MouseEvent e) { + startPt.setLocation(e.getPoint()); + } + public void mouseDragged(MouseEvent e) { + int xd = e.getX() - startPt.x; + int yd = e.getY() - startPt.y; + + Rectangle r = scroll.getVisibleRect(); + r.x -= xd; + r.y -= yd; + scroll.scrollRectToVisible(r); + } +} diff --git a/altosui/Info.plist.in b/altosui/Info.plist.in new file mode 100644 index 00000000..46dea171 --- /dev/null +++ b/altosui/Info.plist.in @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.9"> +<dict> + <key>CFBundleName</key> + <string>AltosUI</string> + <key>CFBundleVersion</key> + <string>@VERSION@</string> + <key>CFBundleAllowMixedLocalizations</key> + <string>true</string> + <key>CFBundleExecutable</key> + <string>JavaApplicationStub</string> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleIdentifier</key> + <string>org.altusmetrum.altosui</string> + <key>CFBundleSignature</key> + <string>Altu</string> + <key>CFBundleGetInfoString</key> + <string>AltOS UI version @VERSION@</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleIconFile</key> + <string>AltosUIIcon.icns</string> + <key>Java</key> + <dict> + <key>MainClass</key> + <string>altosui.AltosUI</string> + <key>JVMVersion</key> + <string>1.5+</string> + <key>ClassPath</key> + <array> + <string>$JAVAROOT/altosui.jar</string> + <string>$JAVAROOT/freetts.jar</string> + </array> + <key>VMOptions</key> + <array> + <string>-Xms512M</string> + <string>-Xmx512M</string> + <string>-Dosgi.clean=true</string> + </array> + </dict> +</dict> +</plist> diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi b/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi new file mode 100644 index 00000000..3ed821eb --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/Example.nsi @@ -0,0 +1,84 @@ +#
+# InstDrv Example, (c) 2003 Jan Kiszka (Jan Kiszka@web.de)
+#
+
+Name "InstDrv.dll test"
+
+OutFile "InstDrv-Test.exe"
+
+ShowInstDetails show
+
+ComponentText "InstDrv Plugin Usage Example"
+
+Page components
+Page instfiles
+
+Section "Install a Driver" InstDriver
+ InstDrv::InitDriverSetup /NOUNLOAD "{4d36e978-e325-11ce-bfc1-08002be10318}" "IrCOMM2k"
+ Pop $0
+ DetailPrint "InitDriverSetup: $0"
+
+ InstDrv::DeleteOemInfFiles /NOUNLOAD
+ Pop $0
+ DetailPrint "DeleteOemInfFiles: $0"
+ StrCmp $0 "00000000" PrintInfNames ContInst1
+
+ PrintInfNames:
+ Pop $0
+ DetailPrint "Deleted $0"
+ Pop $0
+ DetailPrint "Deleted $0"
+
+ ContInst1:
+ InstDrv::CreateDevice /NOUNLOAD
+ Pop $0
+ DetailPrint "CreateDevice: $0"
+
+ SetOutPath $TEMP
+ File "ircomm2k.inf"
+ File "ircomm2k.sys"
+
+ InstDrv::InstallDriver /NOUNLOAD "$TEMP\ircomm2k.inf"
+ Pop $0
+ DetailPrint "InstallDriver: $0"
+ StrCmp $0 "00000000" PrintReboot ContInst2
+
+ PrintReboot:
+ Pop $0
+ DetailPrint "Reboot: $0"
+
+ ContInst2:
+ InstDrv::CountDevices
+ Pop $0
+ DetailPrint "CountDevices: $0"
+SectionEnd
+
+Section "Uninstall the driver again" UninstDriver
+ InstDrv::InitDriverSetup /NOUNLOAD "{4d36e978-e325-11ce-bfc1-08002be10318}" "IrCOMM2k"
+ Pop $0
+ DetailPrint "InitDriverSetup: $0"
+
+ InstDrv::DeleteOemInfFiles /NOUNLOAD
+ Pop $0
+ DetailPrint "DeleteOemInfFiles: $0"
+ StrCmp $0 "00000000" PrintInfNames ContUninst1
+
+ PrintInfNames:
+ Pop $0
+ DetailPrint "Deleted $0"
+ Pop $0
+ DetailPrint "Deleted $0"
+
+ ContUninst1:
+ InstDrv::RemoveAllDevices
+ Pop $0
+ DetailPrint "RemoveAllDevices: $0"
+ StrCmp $0 "00000000" PrintReboot ContUninst2
+
+ PrintReboot:
+ Pop $0
+ DetailPrint "Reboot: $0"
+
+ ContUninst2:
+ Delete "$SYSDIR\system32\ircomm2k.sys"
+SectionEnd
\ No newline at end of file diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe Binary files differnew file mode 100644 index 00000000..615bae15 --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv-Test.exe diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c new file mode 100644 index 00000000..efe866e9 --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.c @@ -0,0 +1,704 @@ +/*
+
+InstDrv.dll - Installs or Removes Device Drivers
+
+Copyright © 2003 Jan Kiszka (Jan.Kiszka@web.de)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented;
+ you must not claim that you wrote the original software.
+ If you use this software in a product, an acknowledgment in the
+ product documentation would be appreciated but is not required.
+2. Altered versions must be plainly marked as such,
+ and must not be misrepresented as being the original software.
+3. This notice may not be removed or altered from any distribution.
+
+*/
+
+
+#include <windows.h>
+#include <setupapi.h>
+#include <newdev.h>
+#include "../exdll/exdll.h"
+
+
+char paramBuf[1024];
+GUID devClass;
+char hwIdBuf[1024];
+int initialized = 0;
+
+
+
+void* memset(void* dst, int val, unsigned int len)
+{
+ while (len-- > 0)
+ *((char *)dst)++ = val;
+
+ return NULL;
+}
+
+
+
+void* memcpy(void* dst, const void* src, unsigned int len)
+{
+ while (len-- > 0)
+ *((char *)dst)++ = *((char *)src)++;
+
+ return NULL;
+}
+
+
+
+int HexCharToInt(char c)
+{
+ if ((c >= '0') && (c <= '9'))
+ return c - '0';
+ else if ((c >= 'a') && (c <= 'f'))
+ return c - 'a' + 10;
+ else if ((c >= 'A') && (c <= 'F'))
+ return c - 'A' + 10;
+ else
+ return -1;
+}
+
+
+
+BOOLEAN HexStringToUInt(char* str, int width, void* valBuf)
+{
+ int i, val;
+
+
+ for (i = width - 4; i >= 0; i -= 4)
+ {
+ val = HexCharToInt(*str++);
+ if (val < 0)
+ return FALSE;
+ *(unsigned int *)valBuf += val << i;
+ }
+
+ return TRUE;
+}
+
+
+
+BOOLEAN StringToGUID(char* guidStr, GUID* pGuid)
+{
+ int i;
+
+
+ memset(pGuid, 0, sizeof(GUID));
+
+ if (*guidStr++ != '{')
+ return FALSE;
+
+ if (!HexStringToUInt(guidStr, 32, &pGuid->Data1))
+ return FALSE;
+ guidStr += 8;
+
+ if (*guidStr++ != '-')
+ return FALSE;
+
+ if (!HexStringToUInt(guidStr, 16, &pGuid->Data2))
+ return FALSE;
+ guidStr += 4;
+
+ if (*guidStr++ != '-')
+ return FALSE;
+
+ if (!HexStringToUInt(guidStr, 16, &pGuid->Data3))
+ return FALSE;
+ guidStr += 4;
+
+ if (*guidStr++ != '-')
+ return FALSE;
+
+ for (i = 0; i < 2; i++)
+ {
+ if (!HexStringToUInt(guidStr, 8, &pGuid->Data4[i]))
+ return FALSE;
+ guidStr += 2;
+ }
+
+ if (*guidStr++ != '-')
+ return FALSE;
+
+ for (i = 2; i < 8; i++)
+ {
+ if (!HexStringToUInt(guidStr, 8, &pGuid->Data4[i]))
+ return FALSE;
+ guidStr += 2;
+ }
+
+ if (*guidStr++ != '}')
+ return FALSE;
+
+ return TRUE;
+}
+
+
+
+DWORD FindNextDevice(HDEVINFO devInfoSet, SP_DEVINFO_DATA* pDevInfoData, DWORD* pIndex)
+{
+ DWORD buffersize = 0;
+ LPTSTR buffer = NULL;
+ DWORD dataType;
+ DWORD result;
+
+
+ while (1)
+ {
+ if (!SetupDiEnumDeviceInfo(devInfoSet, (*pIndex)++, pDevInfoData))
+ {
+ result = GetLastError();
+ break;
+ }
+
+ GetDeviceRegistryProperty:
+ if (!SetupDiGetDeviceRegistryProperty(devInfoSet, pDevInfoData, SPDRP_HARDWAREID,
+ &dataType, (PBYTE)buffer, buffersize,
+ &buffersize))
+ {
+ result = GetLastError();
+
+ if (result == ERROR_INSUFFICIENT_BUFFER)
+ {
+ if (buffer != NULL)
+ LocalFree(buffer);
+
+ buffer = (LPTSTR)LocalAlloc(LPTR, buffersize);
+
+ if (buffer == NULL)
+ break;
+
+ goto GetDeviceRegistryProperty;
+ }
+ else if (result == ERROR_INVALID_DATA)
+ continue; // ignore invalid entries
+ else
+ break; // break on other errors
+ }
+
+ if (lstrcmpi(buffer, hwIdBuf) == 0)
+ {
+ result = 0;
+ break;
+ }
+ }
+
+ if (buffer != NULL)
+ LocalFree(buffer);
+
+ return result;
+}
+
+
+
+DWORD FindFirstDevice(HWND hwndParent, const GUID* pDevClass, const LPTSTR hwId,
+ HDEVINFO* pDevInfoSet, SP_DEVINFO_DATA* pDevInfoData,
+ DWORD *pIndex, DWORD flags)
+{
+ DWORD result;
+
+
+ *pDevInfoSet = SetupDiGetClassDevs((GUID*)pDevClass, NULL, hwndParent, flags);
+ if (*pDevInfoSet == INVALID_HANDLE_VALUE)
+ return GetLastError();
+
+ pDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
+ *pIndex = 0;
+
+ result = FindNextDevice(*pDevInfoSet, pDevInfoData, pIndex);
+
+ if (result != 0)
+ SetupDiDestroyDeviceInfoList(*pDevInfoSet);
+
+ return result;
+}
+
+
+
+/*
+ * InstDrv::InitDriverSetup devClass drvHWID
+ *
+ * devClass - GUID of the driver's device setup class
+ * drvHWID - Hardware ID of the supported device
+ *
+ * Return:
+ * result - error message, empty on success
+ */
+void __declspec(dllexport) InitDriverSetup(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ EXDLL_INIT();
+
+ /* convert class GUID */
+ popstring(paramBuf);
+
+ if (!StringToGUID(paramBuf, &devClass))
+ {
+ popstring(paramBuf);
+ pushstring("Invalid GUID!");
+ return;
+ }
+
+ /* get hardware ID */
+ memset(hwIdBuf, 0, sizeof(hwIdBuf));
+ popstring(hwIdBuf);
+
+ initialized = 1;
+ pushstring("");
+}
+
+
+
+/*
+ * InstDrv::CountDevices
+ *
+ * Return:
+ * result - Number of installed devices the driver supports
+ */
+void __declspec(dllexport) CountDevices(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ HDEVINFO devInfoSet;
+ SP_DEVINFO_DATA devInfoData;
+ int count = 0;
+ char countBuf[16];
+ DWORD index;
+ DWORD result;
+
+
+ EXDLL_INIT();
+
+ if (!initialized)
+ {
+ pushstring("Fatal error!");
+ return;
+ }
+
+ result = FindFirstDevice(hwndParent, &devClass, hwIdBuf, &devInfoSet, &devInfoData,
+ &index, DIGCF_PRESENT);
+ if (result != 0)
+ {
+ pushstring("0");
+ return;
+ }
+
+ do
+ {
+ count++;
+ } while (FindNextDevice(devInfoSet, &devInfoData, &index) == 0);
+
+ SetupDiDestroyDeviceInfoList(devInfoSet);
+
+ wsprintf(countBuf, "%d", count);
+ pushstring(countBuf);
+}
+
+
+
+/*
+ * InstDrv::CreateDevice
+ *
+ * Return:
+ * result - Windows error code
+ */
+void __declspec(dllexport) CreateDevice(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ HDEVINFO devInfoSet;
+ SP_DEVINFO_DATA devInfoData;
+ DWORD result = 0;
+ char resultBuf[16];
+
+
+ EXDLL_INIT();
+
+ if (!initialized)
+ {
+ pushstring("Fatal error!");
+ return;
+ }
+
+ devInfoSet = SetupDiCreateDeviceInfoList(&devClass, hwndParent);
+ if (devInfoSet == INVALID_HANDLE_VALUE)
+ {
+ wsprintf(resultBuf, "%08X", GetLastError());
+ pushstring(resultBuf);
+ return;
+ }
+
+ devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
+ if (!SetupDiCreateDeviceInfo(devInfoSet, hwIdBuf, &devClass, NULL,
+ hwndParent, DICD_GENERATE_ID, &devInfoData))
+ {
+ result = GetLastError();
+ goto InstallCleanup;
+ }
+
+ if (!SetupDiSetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_HARDWAREID,
+ hwIdBuf, (lstrlen(hwIdBuf)+2)*sizeof(TCHAR)))
+ {
+ result = GetLastError();
+ goto InstallCleanup;
+ }
+
+ if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, devInfoSet, &devInfoData))
+ result = GetLastError();
+
+ InstallCleanup:
+ SetupDiDestroyDeviceInfoList(devInfoSet);
+
+ wsprintf(resultBuf, "%08X", result);
+ pushstring(resultBuf);
+}
+
+
+
+/*
+ * InstDrv::InstallDriver infPath
+ *
+ * Return:
+ * result - Windows error code
+ * reboot - non-zero if reboot is required
+ */
+void __declspec(dllexport) InstallDriver(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ char resultBuf[16];
+ BOOL reboot;
+
+
+ EXDLL_INIT();
+ popstring(paramBuf);
+
+ if (!initialized)
+ {
+ pushstring("Fatal error!");
+ return;
+ }
+
+ if (!UpdateDriverForPlugAndPlayDevices(hwndParent, hwIdBuf, paramBuf,
+ INSTALLFLAG_FORCE, &reboot))
+ {
+ wsprintf(resultBuf, "%08X", GetLastError());
+ pushstring(resultBuf);
+ }
+ else
+ {
+ wsprintf(resultBuf, "%d", reboot);
+ pushstring(resultBuf);
+ pushstring("00000000");
+ }
+}
+
+
+
+/*
+ * InstDrv::DeleteOemInfFiles
+ *
+ * Return:
+ * result - Windows error code
+ * oeminf - Path of the deleted devices setup file (oemXX.inf)
+ * oempnf - Path of the deleted devices setup file (oemXX.pnf)
+ */
+void __declspec(dllexport) DeleteOemInfFiles(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ HDEVINFO devInfo;
+ SP_DEVINFO_DATA devInfoData;
+ SP_DRVINFO_DATA drvInfoData;
+ SP_DRVINFO_DETAIL_DATA drvInfoDetail;
+ DWORD index;
+ DWORD result;
+ char resultBuf[16];
+
+
+ if (!initialized)
+ {
+ pushstring("Fatal error!");
+ return;
+ }
+
+ result = FindFirstDevice(NULL, &devClass, hwIdBuf, &devInfo, &devInfoData, &index, 0);
+ if (result != 0)
+ goto Cleanup1;
+
+ if (!SetupDiBuildDriverInfoList(devInfo, &devInfoData, SPDIT_COMPATDRIVER))
+ {
+ result = GetLastError();
+ goto Cleanup2;
+ }
+
+ drvInfoData.cbSize = sizeof(SP_DRVINFO_DATA);
+ drvInfoDetail.cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
+
+ if (!SetupDiEnumDriverInfo(devInfo, &devInfoData, SPDIT_COMPATDRIVER, 0, &drvInfoData))
+ {
+ result = GetLastError();
+ goto Cleanup3;
+ }
+
+ if (!SetupDiGetDriverInfoDetail(devInfo, &devInfoData, &drvInfoData,
+ &drvInfoDetail, sizeof(drvInfoDetail), NULL))
+ {
+ result = GetLastError();
+
+ if (result != ERROR_INSUFFICIENT_BUFFER)
+ goto Cleanup3;
+
+ result = 0;
+ }
+
+ pushstring(drvInfoDetail.InfFileName);
+ if (!DeleteFile(drvInfoDetail.InfFileName))
+ result = GetLastError();
+ else
+ {
+ index = lstrlen(drvInfoDetail.InfFileName);
+ if (index > 3)
+ {
+ lstrcpy(drvInfoDetail.InfFileName+index-3, "pnf");
+ pushstring(drvInfoDetail.InfFileName);
+ if (!DeleteFile(drvInfoDetail.InfFileName))
+ result = GetLastError();
+ }
+ }
+
+ Cleanup3:
+ SetupDiDestroyDriverInfoList(devInfo, &devInfoData, SPDIT_COMPATDRIVER);
+
+ Cleanup2:
+ SetupDiDestroyDeviceInfoList(devInfo);
+
+ Cleanup1:
+ wsprintf(resultBuf, "%08X", result);
+ pushstring(resultBuf);
+}
+
+
+
+/*
+ * InstDrv::RemoveAllDevices
+ *
+ * Return:
+ * result - Windows error code
+ * reboot - non-zero if reboot is required
+ */
+void __declspec(dllexport) RemoveAllDevices(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ HDEVINFO devInfo;
+ SP_DEVINFO_DATA devInfoData;
+ DWORD index;
+ DWORD result;
+ char resultBuf[16];
+ BOOL reboot = FALSE;
+ SP_DEVINSTALL_PARAMS instParams;
+
+
+ EXDLL_INIT();
+
+ if (!initialized)
+ {
+ pushstring("Fatal error!");
+ return;
+ }
+
+ result = FindFirstDevice(NULL, &devClass, hwIdBuf, &devInfo, &devInfoData, &index, 0);
+ if (result != 0)
+ goto Cleanup1;
+
+ do
+ {
+ if (!SetupDiCallClassInstaller(DIF_REMOVE, devInfo, &devInfoData))
+ {
+ result = GetLastError();
+ break;
+ }
+
+ instParams.cbSize = sizeof(instParams);
+ if (!reboot &&
+ SetupDiGetDeviceInstallParams(devInfo, &devInfoData, &instParams) &&
+ ((instParams.Flags & (DI_NEEDRESTART|DI_NEEDREBOOT)) != 0))
+ {
+ reboot = TRUE;
+ }
+
+ result = FindNextDevice(devInfo, &devInfoData, &index);
+ } while (result == 0);
+
+ SetupDiDestroyDeviceInfoList(devInfo);
+
+ Cleanup1:
+ if ((result == 0) || (result == ERROR_NO_MORE_ITEMS))
+ {
+ wsprintf(resultBuf, "%d", reboot);
+ pushstring(resultBuf);
+ pushstring("00000000");
+ }
+ else
+ {
+ wsprintf(resultBuf, "%08X", result);
+ pushstring(resultBuf);
+ }
+}
+
+
+
+/*
+ * InstDrv::StartSystemService serviceName
+ *
+ * Return:
+ * result - Windows error code
+ */
+void __declspec(dllexport) StartSystemService(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ SC_HANDLE managerHndl;
+ SC_HANDLE svcHndl;
+ SERVICE_STATUS svcStatus;
+ DWORD oldCheckPoint;
+ DWORD result;
+ char resultBuf[16];
+
+
+ EXDLL_INIT();
+ popstring(paramBuf);
+
+ managerHndl = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (managerHndl == NULL)
+ {
+ result = GetLastError();
+ goto Cleanup1;
+ }
+
+ svcHndl = OpenService(managerHndl, paramBuf, SERVICE_START | SERVICE_QUERY_STATUS);
+ if (svcHndl == NULL)
+ {
+ result = GetLastError();
+ goto Cleanup2;
+ }
+
+ if (!StartService(svcHndl, 0, NULL) || !QueryServiceStatus(svcHndl, &svcStatus))
+ {
+ result = GetLastError();
+ goto Cleanup3;
+ }
+
+ while (svcStatus.dwCurrentState == SERVICE_START_PENDING)
+ {
+ oldCheckPoint = svcStatus.dwCheckPoint;
+
+ Sleep(svcStatus.dwWaitHint);
+
+ if (!QueryServiceStatus(svcHndl, &svcStatus))
+ {
+ result = GetLastError();
+ break;
+ }
+
+ if (oldCheckPoint >= svcStatus.dwCheckPoint)
+ {
+ if ((svcStatus.dwCurrentState == SERVICE_STOPPED) &&
+ (svcStatus.dwWin32ExitCode != 0))
+ result = svcStatus.dwWin32ExitCode;
+ else
+ result = ERROR_SERVICE_REQUEST_TIMEOUT;
+ }
+ }
+
+ if (svcStatus.dwCurrentState == SERVICE_RUNNING)
+ result = 0;
+
+ Cleanup3:
+ CloseServiceHandle(svcHndl);
+
+ Cleanup2:
+ CloseServiceHandle(managerHndl);
+
+ Cleanup1:
+ wsprintf(resultBuf, "%08X", result);
+ pushstring(resultBuf);
+}
+
+
+
+/*
+ * InstDrv::StopSystemService serviceName
+ *
+ * Return:
+ * result - Windows error code
+ */
+void __declspec(dllexport) StopSystemService(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
+{
+ SC_HANDLE managerHndl;
+ SC_HANDLE svcHndl;
+ SERVICE_STATUS svcStatus;
+ DWORD oldCheckPoint;
+ DWORD result;
+ char resultBuf[16];
+
+
+ EXDLL_INIT();
+ popstring(paramBuf);
+
+ managerHndl = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (managerHndl == NULL)
+ {
+ result = GetLastError();
+ goto Cleanup1;
+ }
+
+ svcHndl = OpenService(managerHndl, paramBuf, SERVICE_STOP | SERVICE_QUERY_STATUS);
+ if (svcHndl == NULL)
+ {
+ result = GetLastError();
+ goto Cleanup2;
+ }
+
+ if (!ControlService(svcHndl, SERVICE_CONTROL_STOP, &svcStatus))
+ {
+ result = GetLastError();
+ goto Cleanup3;
+ }
+
+ while (svcStatus.dwCurrentState == SERVICE_STOP_PENDING)
+ {
+ oldCheckPoint = svcStatus.dwCheckPoint;
+
+ Sleep(svcStatus.dwWaitHint);
+
+ if (!QueryServiceStatus(svcHndl, &svcStatus))
+ {
+ result = GetLastError();
+ break;
+ }
+
+ if (oldCheckPoint >= svcStatus.dwCheckPoint)
+ {
+ result = ERROR_SERVICE_REQUEST_TIMEOUT;
+ break;
+ }
+ }
+
+ if (svcStatus.dwCurrentState == SERVICE_STOPPED)
+ result = 0;
+
+ Cleanup3:
+ CloseServiceHandle(svcHndl);
+
+ Cleanup2:
+ CloseServiceHandle(managerHndl);
+
+ Cleanup1:
+ wsprintf(resultBuf, "%08X", result);
+ pushstring(resultBuf);
+}
+
+
+
+BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
+{
+ return TRUE;
+}
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp new file mode 100644 index 00000000..874e66c7 --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsp @@ -0,0 +1,110 @@ +# Microsoft Developer Studio Project File - Name="InstDrv" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** NICHT BEARBEITEN **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=InstDrv - Win32 Debug
+!MESSAGE Dies ist kein gültiges Makefile. Zum Erstellen dieses Projekts mit NMAKE
+!MESSAGE verwenden Sie den Befehl "Makefile exportieren" und führen Sie den Befehl
+!MESSAGE
+!MESSAGE NMAKE /f "InstDrv.mak".
+!MESSAGE
+!MESSAGE Sie können beim Ausführen von NMAKE eine Konfiguration angeben
+!MESSAGE durch Definieren des Makros CFG in der Befehlszeile. Zum Beispiel:
+!MESSAGE
+!MESSAGE NMAKE /f "InstDrv.mak" CFG="InstDrv - Win32 Debug"
+!MESSAGE
+!MESSAGE Für die Konfiguration stehen zur Auswahl:
+!MESSAGE
+!MESSAGE "InstDrv - Win32 Release" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "InstDrv - Win32 Debug" (basierend auf "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "InstDrv - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 1
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O1 /I "C:\Programme\WINDDK\3790\inc\ddk\w2k" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /FD /c
+# SUBTRACT CPP /YX
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x407 /d "NDEBUG"
+# ADD RSC /l 0x407 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib comdlg32.lib advapi32.lib shell32.lib setupapi.lib newdev.lib /nologo /entry:"_DllMainCRTStartup" /dll /machine:I386 /nodefaultlib /out:"../../Plugins/InstDrv.dll" /libpath:"C:\Programme\WINDDK\3790\lib\w2k\i386" /opt:nowin98
+# SUBTRACT LINK32 /pdb:none
+
+!ELSEIF "$(CFG)" == "InstDrv - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "INSTDRV_EXPORTS" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x407 /d "_DEBUG"
+# ADD RSC /l 0x407 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /entry:"_DllMainCRTStartup" /dll /debug /machine:I386 /pdbtype:sept
+# SUBTRACT LINK32 /nodefaultlib
+
+!ENDIF
+
+# Begin Target
+
+# Name "InstDrv - Win32 Release"
+# Name "InstDrv - Win32 Debug"
+# Begin Group "Quellcodedateien"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\InstDrv.c
+# End Source File
+# End Group
+# Begin Group "Header-Dateien"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Ressourcendateien"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw new file mode 100644 index 00000000..b3d02f0e --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/InstDrv.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNUNG: DIESE ARBEITSBEREICHSDATEI DARF NICHT BEARBEITET ODER GELÖSCHT WERDEN!
+
+###############################################################################
+
+Project: "InstDrv"=.\InstDrv.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt b/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt new file mode 100644 index 00000000..e5877aa6 --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/Readme.txt @@ -0,0 +1,141 @@ +InstDrv.dll version 0.2 - Installs or Removes Device Drivers
+------------------------------------------------------------
+
+
+The plugin helps you to create NSIS scripts for installing device drivers or
+removing them again. It can count installed device instances, create new ones
+or delete all supported device. InstDrv works on Windows 2000 or later.
+
+
+
+InstDrv::InitDriverSetup devClass drvHWID
+Return: result
+
+To start processing a driver, first call this function. devClass is the GUID
+of the device class the driver supports, drvHWID is the device hardware ID. If
+you don't know what these terms mean, you may want to take a look at the
+Windows DDK. This function returns an empty string on success, otherwise an
+error message.
+
+InitDriverSetup has to be called every time after the plugin dll has been
+(re-)loaded, or if you want to switch to a different driver.
+
+
+
+InstDrv::CountDevices
+Return: number
+
+This call returns the number of installed and supported devices of the driver.
+
+
+
+InstDrv::CreateDevice
+Return: result
+
+To create a new deviced node which the driver has to support, use this
+function. You may even call it multiple times for more than one instance. The
+return value is the Windows error code (in hex). Use CreateDevice before
+installing or updating the driver itself.
+
+
+
+InstDrv::InstallDriver infPath
+Return: result
+ reboot
+
+InstallDriver installs or updates a device driver as specified in the .inf
+setup script. It returns a Windows error code (in hex) and, on success, a flag
+signalling if a system reboot is required.
+
+
+
+InstDrv::DeleteOemInfFiles
+Return: result
+ oeminf
+ oempnf
+
+DeleteOemInfFiles tries to clean up the Windows inf directory by deleting the
+oemXX.inf and oemXX.pnf files associated with the drivers. It returns a
+Windows error code (in hex) and, on success, the names of the deleted files.
+This functions requires that at least one device instance is still present.
+So, call it before you remove the devices itself. You should also call it
+before updating a driver. This avoids that the inf directory gets slowly
+messed up with useless old setup scripts (which does NOT really accelerate
+Windows). The error code which comes up when no device is installed is
+"00000103".
+
+
+
+InstDrv::RemoveAllDevices
+Return: result
+ reboot
+
+This functions deletes all devices instances the driver supported. It returns
+a Windows error code (in hex) and, on success, a flag signalling if the system
+needs to be rebooted. You additionally have to remove the driver binaries from
+the system paths.
+
+
+
+InstDrv::StartSystemService serviceName
+Return: result
+
+Call this function to start the provided system service. The function blocks
+until the service is started or the system reported a timeout. The return value
+is the Windows error code (in hex).
+
+
+
+InstDrv::StopSystemService serviceName
+Return: result
+
+This function tries to stop the provided system service. It blocks until the
+service has been shut down or the system reported a timeout. The return value
+is the Windows error code (in hex).
+
+
+
+Example.nsi
+
+The example script installs or removes the virtual COM port driver of IrCOMM2k
+(2.0.0-alpha8, see www.ircomm2k.de/english). The driver and its setup script
+are only included for demonstration purposes, they do not work without the
+rest of IrCOMM2k (but they also do not cause any harm).
+
+
+
+Building the Source Code
+
+To build the plugin from the source code, some include files and libraries
+which come with the Windows DDK are required.
+
+
+
+History
+
+ 0.2 - fixed bug when calling InitDriverSetup the second time
+ - added StartSystemService and StopSystemService
+
+ 0.1 - first release
+
+
+
+License
+
+Copyright © 2003 Jan Kiszka (Jan.Kiszka@web.de)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented;
+ you must not claim that you wrote the original software.
+ If you use this software in a product, an acknowledgment in the
+ product documentation would be appreciated but is not required.
+2. Altered versions must be plainly marked as such,
+ and must not be misrepresented as being the original software.
+3. This notice may not be removed or altered from any distribution.
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf new file mode 100644 index 00000000..ccda1d87 --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.inf @@ -0,0 +1,137 @@ +; IrCOMM2k.inf
+;
+; Installation file for the Virtual Infrared-COM-Port
+;
+; (c) Copyright 2001, 2002 Jan Kiszka
+;
+
+[Version]
+Signature="$Windows NT$"
+Provider=%JK%
+Class=Ports
+ClassGUID={4d36e978-e325-11ce-bfc1-08002be10318}
+;DriverVer=03/26/2002,1.2.1.0
+
+[DestinationDirs]
+IrCOMM2k.Copy2Drivers = 12
+IrCOMM2k.Copy2Winnt = 10
+IrCOMM2k.Copy2System32 = 11
+IrCOMM2k.Copy2Help = 18
+
+
+;
+; Driver information
+;
+
+[Manufacturer]
+%JK% = JK.Mfg
+
+[JK.Mfg]
+%JK.DeviceDescIrCOMM% = IrCOMM2k_inst,IrCOMM2k
+
+
+;
+; General installation section
+;
+
+[IrCOMM2k_inst]
+CopyFiles = IrCOMM2k.Copy2Drivers ;,IrCOMM2k.Copy2System32,IrCOMM2k.Copy2Help,IrCOMM2k.Copy2Winnt
+;AddReg = IrCOMM2k_inst_AddReg
+
+
+;
+; File sections
+;
+
+[IrCOMM2k.Copy2Drivers]
+ircomm2k.sys,,,2
+
+;[IrCOMM2k.Copy2System32]
+;ircomm2k.exe,,,2
+;ircomm2k.dll,,,2
+
+;[IrCOMM2k.Copy2Help]
+;ircomm2k.hlp,,,2
+
+;[IrCOMM2k.Copy2Winnt]
+;IrCOMM2k-Setup.exe,Setup.exe,,2
+
+
+;
+; Service Installation
+;
+
+[IrCOMM2k_inst.Services]
+AddService = IrCOMM2k,0x00000002,IrCOMM2k_DriverService_Inst,IrCOMM2k_DriverEventLog_Inst
+;AddService = IrCOMM2kSvc,,IrCOMM2k_Service_Inst
+
+[IrCOMM2k_DriverService_Inst]
+DisplayName = %IrCOMM2k.DrvName%
+ServiceType = 1 ; SERVICE_KERNEL_DRIVER
+StartType = 3 ; SERVICE_DEMAND_START
+ErrorControl = 0 ; SERVICE_ERROR_IGNORE
+ServiceBinary = %12%\ircomm2k.sys
+
+;[IrCOMM2k_Service_Inst]
+;DisplayName = %IrCOMM2k.SvcName%
+;Description = %IrCOMM2k.SvcDesc%
+;ServiceType = 0x00000120 ; SERVICE_WIN32_SHARE_PROCESS, SERVICE_INTERACTIVE_PROCESS
+;StartType = 2 ; SERVICE_AUTO_START
+;ErrorControl = 0 ; SERVICE_ERROR_IGNORE
+;ServiceBinary = %11%\ircomm2k.exe
+;Dependencies = IrCOMM2k
+;AddReg = IrCOMM2kSvcAddReg
+
+
+[IrCOMM2k_inst.nt.HW]
+AddReg=IrCOMM2kHwAddReg
+
+[IrCOMM2kHwAddReg]
+HKR,,PortSubClass,REG_BINARY,0x00000001
+;HKR,,TimeoutScaling,REG_DWORD,0x00000001
+;HKR,,StatusLines,REG_DWORD,0x00000000
+
+;[IrCOMM2k_inst_AddReg]
+;HKR,,EnumPropPages32,,"ircomm2k.dll,IrCOMM2kPropPageProvider"
+;HKLM,%UNINSTALL_KEY%,DisplayIcon,0x00020000,"%windir%\IrCOMM2k-Setup.exe"
+;HKLM,%UNINSTALL_KEY%,DisplayName,,"IrCOMM2k 1.2.1 "
+;HKLM,%UNINSTALL_KEY%,DisplayVersion,,"1.2.1"
+;HKLM,%UNINSTALL_KEY%,HelpLink,,"http://www.ircomm2k.de"
+;HKLM,%UNINSTALL_KEY%,Publisher,,%JK%
+;HKLM,%UNINSTALL_KEY%,UninstallString,0x00020000,"%windir%\IrCOMM2k-Setup.exe"
+
+;[IrCOMM2kSvcAddReg]
+;HKR,Parameters,ActiveConnectOnly,REG_DWORD,0x00000000
+
+
+[IrCOMM2k_DriverEventLog_Inst]
+AddReg = IrCOMM2k_DriverEventLog_AddReg
+
+[IrCOMM2k_DriverEventLog_AddReg]
+HKR,,EventMessageFile,REG_EXPAND_SZ,"%SystemRoot%\System32\IoLogMsg.dll;%SystemRoot%\System32\drivers\ircomm2k.sys"
+HKR,,TypesSupported,REG_DWORD,7
+
+
+[Strings]
+
+;
+; Non-Localizable Strings
+;
+
+REG_SZ = 0x00000000
+REG_MULTI_SZ = 0x00010000
+REG_EXPAND_SZ = 0x00020000
+REG_BINARY = 0x00000001
+REG_DWORD = 0x00010001
+SERVICEROOT = "System\CurrentControlSet\Services"
+UNINSTALL_KEY = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\IrCOMM2k"
+
+;
+; Localizable Strings
+;
+
+JK = "Jan Kiszka"
+JK.DeviceDescIrCOMM = "Virtueller Infrarot-Kommunikationsanschluss"
+IrCOMM2k.DrvName = "Virtueller Infrarot-Kommunikationsanschluss"
+;IrCOMM2k.SvcName = "Virtueller Infrarot-Kommunikationsanschluß, Dienstprogramm"
+;IrCOMM2k.SvcDesc = "Bildet über Infarot einen Kommunikationsanschluß nach."
diff --git a/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys Binary files differnew file mode 100644 index 00000000..7882583b --- /dev/null +++ b/altosui/Instdrv/NSIS/Contrib/InstDrv/ircomm2k.sys diff --git a/altosui/Instdrv/NSIS/Plugins/InstDrv.dll b/altosui/Instdrv/NSIS/Plugins/InstDrv.dll Binary files differnew file mode 100644 index 00000000..482e955e --- /dev/null +++ b/altosui/Instdrv/NSIS/Plugins/InstDrv.dll diff --git a/altosui/Makefile-standalone b/altosui/Makefile-standalone new file mode 100644 index 00000000..0d9931d5 --- /dev/null +++ b/altosui/Makefile-standalone @@ -0,0 +1,184 @@ +.SUFFIXES: .java .class + +CLASSPATH=classes:./*:/usr/share/java/* +CLASSFILES=\ + Altos.class \ + AltosChannelMenu.class \ + AltosConfig.class \ + AltosConfigUI.class \ + AltosConvert.class \ + AltosCRCException.class \ + AltosCSV.class \ + AltosCSVUI.class \ + AltosDebug.class \ + AltosEepromDownload.class \ + AltosEepromMonitor.class \ + AltosEepromReader.class \ + AltosEepromRecord.class \ + AltosFile.class \ + AltosFlash.class \ + AltosFlashUI.class \ + AltosFlightInfoTableModel.class \ + AltosFlightStatusTableModel.class \ + AltosGPS.class \ + AltosGreatCircle.class \ + AltosHexfile.class \ + AltosLine.class \ + AltosInfoTable.class \ + AltosLog.class \ + AltosLogfileChooser.class \ + AltosParse.class \ + AltosPreferences.class \ + AltosReader.class \ + AltosRecord.class \ + AltosSerialMonitor.class \ + AltosSerial.class \ + AltosState.class \ + AltosStatusTable.class \ + AltosTelemetry.class \ + AltosTelemetryReader.class \ + AltosUI.class \ + AltosDevice.class \ + AltosDeviceDialog.class \ + AltosRomconfig.class \ + AltosRomconfigUI.class \ + AltosVoice.class + +JAVA_ICON=../icon/altus-metrum-16x16.jpg +WINDOWS_ICON=../icon/altus-metrum.ico + +# where altosui.jar gets installed +ALTOSLIB=/usr/share/java + +# where freetts.jar is to be found +FREETTSLIB=/usr/share/java + +# all of the freetts files +FREETTSJAR= \ + $(FREETTSLIB)/cmudict04.jar \ + $(FREETTSLIB)/cmulex.jar \ + $(FREETTSLIB)/cmu_time_awb.jar \ + $(FREETTSLIB)/cmutimelex.jar \ + $(FREETTSLIB)/cmu_us_kal.jar \ + $(FREETTSLIB)/en_us.jar \ + $(FREETTSLIB)/freetts.jar + +# The current hex files +HEXLIB=../src +HEXFILES = \ + $(HEXLIB)/telemetrum-v1.0.ihx \ + $(HEXLIB)/teledongle-v0.2.ihx + +JAVAFLAGS=-Xlint:unchecked -Xlint:deprecation + +ALTOSUIJAR = altosui.jar +FATJAR = fat/altosui.jar + +OS:=$(shell uname) + +LINUX_APP=altosui + +DARWIN_ZIP=Altos-Mac.zip + +WINDOWS_EXE=Altos-Windows.exe + +LINUX_TGZ=Altos-Linux.tgz + +all: altosui.jar $(LINUX_APP) +fat: altosui.jar $(LINUX_APP) $(DARWIN_ZIP) $(WINDOWS_EXE) $(LINUX_TGZ) + +$(CLASSFILES): + +.java.class: + javac -encoding UTF8 -classpath "$(CLASSPATH)" $(JAVAFLAGS) $*.java + +altosui.jar: classes/images classes/altosui classes/libaltosJNI $(CLASSFILES) Manifest.txt + cd ./classes && jar cfm ../$@ altosui/Manifest.txt images/* altosui/*.class libaltosJNI/*.class + +Manifest.txt: Makefile $(CLASSFILES) + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: $(FREETTSLIB)/freetts.jar" >> $@ + +classes/altosui: + mkdir -p classes + ln -sf .. classes/altosui + +classes/libaltosJNI: + mkdir -p classes + ln -sf ../libaltos/libaltosJNI classes/libaltosJNI + +classes/images: + mkdir -p classes/images + ln -sf ../$(JAVA_ICON) classes/images + +altosui: + echo "#!/bin/sh" > $@ + echo "exec java -Djava.library.path=/usr/lib/altos -jar /usr/share/java/altosui.jar" >> $@ + chmod +x ./altosui + +fat/altosui: + echo "#!/bin/sh" > $@ + echo 'ME=`which "$0"`' >> $@ + echo 'DIR=`dirname "$ME"`' >> $@ + echo 'exec java -Djava.library.path="$$DIR" -jar "$$DIR"/altosui.jar' >> $@ + chmod +x $@ + +fat/altosui.jar: $(CLASSFILES) $(JAVA_ICON) fat/classes/Manifest.txt + mkdir -p fat/classes + test -L fat/classes/altosui || ln -sf ../.. fat/classes/altosui + mkdir -p fat/classes/images + cp $(JAVA_ICON) fat/classes/images + test -L fat/classes/libaltosJNI || ln -sf ../../libaltos/libaltosJNI fat/classes/libaltosJNI + cd ./fat/classes && jar cfm ../../$@ Manifest.txt images/* altosui/*.class libaltosJNI/*.class + +fat/classes/Manifest.txt: $(CLASSFILES) Makefile + mkdir -p fat/classes + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: freetts.jar" >> $@ + +install: altosui.jar altosui + install -m 0644 altosui.jar $(DESTDIR)/usr/share/java/altosui.jar + install -m 0644 altosui.1 $(DESTDIR)/usr/share/man/man1/altosui.1 + install altosui $(DESTDIR)/usr/bin/altosui + +clean: + rm -f *.class altosui.jar + rm -f AltosUI.app/Contents/Resources/Java/* + rm -rf classes + rm -rf windows linux + +distclean: clean + rm -f $(DARWIN_ZIP) $(WINDOWS_EXE) $(LINUX_TGZ) + rm -rf darwin fat + +FAT_FILES=$(FATJAR) $(FREETTSJAR) $(HEXFILES) + +LINUX_FILES=$(FAT_FILES) libaltos/libaltos.so fat/altosui +$(LINUX_TGZ): $(LINUX_FILES) + rm -f $@ + mkdir -p linux/AltOS + rm -f linux/AltOS/* + cp $(LINUX_FILES) linux/AltOS + cd linux && tar czf ../$@ AltOS + +DARWIN_RESOURCES=$(FATJAR) $(FREETTSJAR) libaltos/libaltos.dylib +DARWIN_EXTRA=$(HEXFILES) +DARWIN_FILES=$(DARWIN_RESOURCES) $(DARWIN_EXTRA) + +$(DARWIN_ZIP): $(DARWIN_FILES) + rm -f $@ + cp -a AltosUI.app darwin/ + mkdir -p darwin/AltosUI.app/Contents/Resources/Java + cp $(DARWIN_RESOURCES) darwin/AltosUI.app/Contents/Resources/Java + mkdir -p darwin/AltOS + cp $(DARWIN_EXTRA) darwin/AltOS + cd darwin && zip -r ../$@ AltosUI.app AltOS + +WINDOWS_FILES = $(FAT_FILES) libaltos/altos.dll ../telemetrum.inf $(WINDOWS_ICON) + +$(WINDOWS_EXE): $(WINDOWS_FILES) altos-windows.nsi + rm -f $@ + mkdir -p windows/AltOS + rm -f windows/AltOS/* + cp $(WINDOWS_FILES) windows/AltOS + makensis altos-windows.nsi diff --git a/altosui/Makefile.am b/altosui/Makefile.am new file mode 100644 index 00000000..9f75d5e3 --- /dev/null +++ b/altosui/Makefile.am @@ -0,0 +1,330 @@ +SUBDIRS=libaltos +JAVAROOT=classes +AM_JAVACFLAGS=-encoding UTF-8 -Xlint:deprecation + +man_MANS=altosui.1 + +altoslibdir=$(libdir)/altos + +CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:../altoslib/*:libaltos::$(JFREECHART)/jfreechart.jar:$(FREETTS)/freetts.jar" + +bin_SCRIPTS=altosui + +altosui_BT = \ + AltosBTDevice.java \ + AltosBTDeviceIterator.java \ + AltosBTManage.java \ + AltosBTKnown.java + +altosui_JAVA = \ + GrabNDrag.java \ + AltosAscent.java \ + AltosChannelMenu.java \ + AltosCompanionInfo.java \ + AltosConfig.java \ + AltosConfigFreqUI.java \ + AltosConfigUI.java \ + AltosConfigureUI.java \ + AltosConfigTD.java \ + AltosConfigTDUI.java \ + AltosCSV.java \ + AltosCSVUI.java \ + AltosDebug.java \ + AltosDescent.java \ + AltosDeviceDialog.java \ + AltosDevice.java \ + AltosUSBDevice.java \ + AltosDisplayThread.java \ + AltosEepromDelete.java \ + AltosEepromDownload.java \ + AltosEepromList.java \ + AltosEepromManage.java \ + AltosEepromMonitor.java \ + AltosEepromSelect.java \ + AltosFlash.java \ + AltosFlashUI.java \ + AltosFlightDisplay.java \ + AltosFlightInfoTableModel.java \ + AltosFlightStats.java \ + AltosFlightStatsTable.java \ + AltosFlightStatus.java \ + AltosFlightStatusUpdate.java \ + AltosFlightUI.java \ + AltosFontListener.java \ + AltosFreqList.java \ + AltosHexfile.java \ + Altos.java \ + AltosIdleMonitorUI.java \ + AltosIgniteUI.java \ + AltosLaunch.java \ + AltosLaunchUI.java \ + AltosInfoTable.java \ + AltosKML.java \ + AltosLanded.java \ + AltosLed.java \ + AltosLights.java \ + AltosPad.java \ + AltosUIPreferences.java \ + AltosRomconfig.java \ + AltosRomconfigUI.java \ + AltosScanUI.java \ + AltosSerial.java \ + AltosSerialInUseException.java \ + AltosSiteMap.java \ + AltosSiteMapPreload.java \ + AltosSiteMapCache.java \ + AltosSiteMapTile.java \ + AltosUI.java \ + AltosUIListener.java \ + AltosFrame.java \ + AltosDialog.java \ + AltosWriter.java \ + AltosDataPointReader.java \ + AltosDataPoint.java \ + AltosGraph.java \ + AltosGraphTime.java \ + AltosGraphUI.java \ + AltosDataChooser.java \ + AltosVersion.java \ + AltosVoice.java \ + $(altosui_BT) + +JFREECHART_CLASS= \ + jfreechart.jar + +JCOMMON_CLASS=\ + jcommon.jar + +FREETTS_CLASS= \ + cmudict04.jar \ + cmulex.jar \ + cmu_time_awb.jar \ + cmutimelex.jar \ + cmu_us_kal.jar \ + en_us.jar \ + freetts.jar + +ALTOSLIB_CLASS=\ + AltosLib.jar + +LIBALTOS= \ + libaltos.so \ + libaltos.dylib \ + altos.dll + +JAR=altosui.jar + +FATJAR=altosui-fat.jar + +# Icons +ICONDIR=$(top_srcdir)/icon + +JAVA_ICON=$(ICONDIR)/altus-metrum-16x16.jpg + +ICONS= $(ICONDIR)/redled.png $(ICONDIR)/redoff.png \ + $(ICONDIR)/greenled.png $(ICONDIR)/greenoff.png \ + $(ICONDIR)/grayled.png $(ICONDIR)/grayoff.png + +# icon base names for jar +ICONJAR= -C $(ICONDIR) altus-metrum-16x16.jpg \ + -C $(ICONDIR) redled.png -C $(ICONDIR) redoff.png \ + -C $(ICONDIR) greenled.png -C $(ICONDIR) greenoff.png \ + -C $(ICONDIR) grayon.png -C $(ICONDIR) grayled.png + +WINDOWS_ICON=$(ICONDIR)/altus-metrum.ico + +# Firmware +FIRMWARE_TD_0_2=$(top_srcdir)/src/teledongle-v0.2-$(VERSION).ihx +FIRMWARE_TD=$(FIRMWARE_TD_0_2) + +FIRMWARE_TM_1_0=$(top_srcdir)/src/telemetrum-v1.0-$(VERSION).ihx +FIRMWARE_TM_1_1=$(top_srcdir)/src/telemetrum-v1.1-$(VERSION).ihx +FIRMWARE_TM_1_2=$(top_srcdir)/src/telemetrum-v1.2-$(VERSION).ihx +FIRMWARE_TM=$(FIRMWARE_TM_1_0) $(FIRMWARE_TM_1_1) $(FIRMWARE_TM_1_2) + +FIRMWARE_TELEMINI_1_0=$(top_srcdir)/src/telemini-v1.0-$(VERSION).ihx +FIRMWARE_TELEMINI=$(FIRMWARE_TELEMINI_1_0) + +FIRMWARE=$(FIRMWARE_TM) $(FIRMWARE_TELEMINI) $(FIRMWARE_TD) + +ALTUSMETRUM_DOC=$(top_srcdir)/doc/altusmetrum.pdf +ALTOS_DOC=$(top_srcdir)/doc/altos.pdf +TELEMETRY_DOC=$(top_srcdir)/doc/telemetry.pdf +TEMPLATE_DOC=$(top_srcdir)/doc/telemetrum-outline.pdf $(top_srcdir)/doc/megametrum-outline.pdf + +DOC=$(ALTUSMETRUM_DOC) $(ALTOS_DOC) $(TELEMETRY_DOC) $(TEMPLATE_DOC) + +# Distribution targets +LINUX_DIST=Altos-Linux-$(VERSION).tar.bz2 +MACOSX_DIST=Altos-Mac-$(VERSION).zip +WINDOWS_DIST=Altos-Windows-$(VERSION_DASH).exe + +FAT_FILES=$(FATJAR) $(ALTOSLIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) + +LINUX_FILES=$(FAT_FILES) libaltos.so $(FIRMWARE) $(DOC) +LINUX_EXTRA=altosui-fat + +MACOSX_INFO_PLIST=Info.plist +MACOSX_FILES=$(FAT_FILES) libaltos.dylib $(MACOSX_INFO_PLIST) +MACOSX_EXTRA=$(FIRMWARE) + +WINDOWS_FILES=$(FAT_FILES) altos.dll altos64.dll $(top_srcdir)/telemetrum.inf $(WINDOWS_ICON) + +all-local: classes/altosui $(JAR) altosui altosui-test altosui-jdb + +clean-local: + -rm -rf classes $(JAR) $(FATJAR) \ + $(LINUX_DIST) $(MACOSX_DIST) windows $(WINDOWS_DIST) $(ALTOSLIB_CLASS) $(FREETTS_CLASS) \ + $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) Manifest.txt Manifest-fat.txt altos-windows.log \ + altosui altosui-test altosui-jdb macosx linux + +if FATINSTALL + +FATTARGET=$(FATDIR)/$(VERSION) + +LINUX_DIST_TARGET=$(FATTARGET)/$(LINUX_DIST) +MACOSX_DIST_TARGET=$(FATTARGET)/$(MACOSX_DIST) +WINDOWS_DIST_TARGET=$(FATTARGET)/$(WINDOWS_DIST) + +fat: $(LINUX_DIST_TARGET) $(MACOSX_DIST_TARGET) $(WINDOWS_DIST_TARGET) + +$(LINUX_DIST_TARGET): $(LINUX_DIST) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(MACOSX_DIST_TARGET): $(MACOSX_DIST) + mkdir -p $(FATTARGET) + cp -p $< $@ + +$(WINDOWS_DIST_TARGET): $(WINDOWS_DIST) + mkdir -p $(FATTARGET) + cp -p $< $@ + +else +fat: $(LINUX_DIST) $(MACOSX_DIST) $(WINDOWS_DIST) +endif + + +altosuidir=$(datadir)/java + +install-altosuiJAVA: altosui.jar + @$(NORMAL_INSTALL) + test -z "$(altosuidir)" || $(MKDIR_P) "$(DESTDIR)$(altosuidir)" + echo " $(INSTALL_DATA)" "$<" "'$(DESTDIR)$(altosuidir)/altosui.jar'"; \ + $(INSTALL_DATA) "$<" "$(DESTDIR)$(altosuidir)" + +classes/altosui: + mkdir -p classes/altosui + +$(JAR): classaltosui.stamp Manifest.txt $(JAVA_ICON) $(ALTOSLIB_CLASS) + jar cfm $@ Manifest.txt \ + $(ICONJAR) \ + -C classes altosui \ + -C libaltos libaltosJNI + +$(FATJAR): classaltosui.stamp Manifest-fat.txt $(ALTOSLIB_CLASS) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) $(JAVA_ICON) + jar cfm $@ Manifest-fat.txt \ + $(ICONJAR) \ + -C classes altosui \ + -C libaltos libaltosJNI + +Manifest.txt: Makefile + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: AltosLib.jar $(FREETTS)/freetts.jar $(JFREECHART)/jfreechart.jar $(JCOMMON)/jcommon.jar" >> $@ + +Manifest-fat.txt: + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: AltosLib.jar freetts.jar jfreechart.jar jcommon.jar" >> $@ + +altosui: Makefile + echo "#!/bin/sh" > $@ + echo 'exec java -cp "$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="$(altoslibdir)" -jar "$(altosuidir)/altosui.jar" "$$@"' >> $@ + chmod +x $@ + +altosui-test: Makefile + echo "#!/bin/sh" > $@ + echo 'exec java -cp "./*:$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="libaltos/.libs" -jar altosui.jar "$$@"' >> $@ + chmod +x $@ + +altosui-jdb: Makefile + echo "#!/bin/sh" > $@ + echo 'exec jdb -classpath "classes:libaltos:$(FREETTS)/*:$(JFREECHART)/*:$(JCOMMON)/*" -Djava.library.path="libaltos/.libs" altosui/AltosUI "$$@"' >> $@ + chmod +x $@ + +libaltos.so: build-libaltos + -rm -f "$@" + $(LN_S) libaltos/.libs/"$@" . + +libaltos.dylib: + -rm -f "$@" + $(LN_S) libaltos/"$@" . + +altos.dll: libaltos/altos.dll + -rm -f "$@" + $(LN_S) libaltos/"$@" . + +altos64.dll: libaltos/altos64.dll + -rm -f "$@" + $(LN_S) libaltos/"$@" . + +libaltos/.libs/libaltos.so: build-libaltos + +libaltos/altos.dll: build-altos-dll + +libaltos/altos64.dll: build-altos64-dll + +build-libaltos: + +cd libaltos && make libaltos.la +build-altos-dll: + +cd libaltos && make altos.dll + +build-altos64-dll: + +cd libaltos && make altos64.dll + +$(ALTOSLIB_CLASS): + -rm -f "$@" + $(LN_S) ../altoslib/"$@" . + +$(FREETTS_CLASS): + -rm -f "$@" + $(LN_S) "$(FREETTS)"/"$@" . + +$(JFREECHART_CLASS): + -rm -f "$@" + $(LN_S) "$(JFREECHART)"/"$@" . + +$(JCOMMON_CLASS): + -rm -f "$@" + $(LN_S) "$(JCOMMON)"/"$@" . + +$(LINUX_DIST): $(LINUX_FILES) $(LINUX_EXTRA) + -rm -f $@ + -rm -rf linux + mkdir -p linux/AltOS + cp -p $(LINUX_FILES) linux/AltOS + cp -p altosui-fat linux/AltOS/altosui + chmod +x linux/AltOS/altosui + tar cjf $@ -C linux AltOS + +$(MACOSX_DIST): $(MACOSX_FILES) $(MACOSX_EXTRA) + -rm -f $@ + -rm -rf macosx + mkdir macosx + cp -a AltosUI.app macosx/ + cp -p Info.plist macosx/AltosUI.app/Contents + mkdir -p macosx/AltOS macosx/AltosUI.app/Contents/Resources/Java + cp -p $(FATJAR) macosx/AltosUI.app/Contents/Resources/Java/altosui.jar + cp -p libaltos.dylib macosx/AltosUI.app/Contents/Resources/Java + cp -p $(ALTOSLIB_CLASS) macosx/AltosUI.app/Contents/Resources/Java + cp -p $(FREETTS_CLASS) macosx/AltosUI.app/Contents/Resources/Java + cp -p $(JFREECHART_CLASS) macosx/AltosUI.app/Contents/Resources/Java + cp -p $(JCOMMON_CLASS) macosx/AltosUI.app/Contents/Resources/Java + cp -p $(MACOSX_EXTRA) macosx/AltOS + cd macosx && zip -r ../$@ AltosUI.app AltOS + +$(WINDOWS_DIST): $(WINDOWS_FILES) altos-windows.nsi + -rm -f $@ + makensis -Oaltos-windows.log "-XOutFile $@" "-DVERSION=$(VERSION)" altos-windows.nsi + +publish: + scp launch-sites.txt gag.com:public_html
\ No newline at end of file diff --git a/altosui/altos-windows.nsi b/altosui/altos-windows.nsi new file mode 100644 index 00000000..986919d4 --- /dev/null +++ b/altosui/altos-windows.nsi @@ -0,0 +1,168 @@ +!addplugindir Instdrv/NSIS/Plugins +; Definitions for Java 1.6 Detection +!define JRE_VERSION "1.6" +!define JRE_ALTERNATE "1.7" +!define JRE_URL "http://javadl.sun.com/webapps/download/AutoDL?BundleId=52247&/jre-6u27-windows-i586-p.exe" +!define PRODUCT_NAME "Altus Metrum Windows Software" + +Name "Altus Metrum Installer" + +; Default install directory +InstallDir "$PROGRAMFILES\AltusMetrum" + +; Tell the installer where to re-install a new version +InstallDirRegKey HKLM "Software\AltusMetrum" "Install_Dir" + +LicenseText "GNU General Public License Version 2" +LicenseData "../COPYING" + +; Need admin privs for Vista or Win7 +RequestExecutionLevel admin + +ShowInstDetails Show + +ComponentText "Altus Metrum Software and Driver Installer" + +Function GetJRE + MessageBox MB_OK "${PRODUCT_NAME} uses Java ${JRE_VERSION} 32-bit, it will now \ + be downloaded and installed" + + StrCpy $2 "$TEMP\Java Runtime Environment.exe" + nsisdl::download /TIMEOUT=30000 ${JRE_URL} $2 + Pop $R0 ;Get the return value + StrCmp $R0 "success" +3 + MessageBox MB_OK "Download failed: $R0" + Quit + ExecWait $2 + Delete $2 +FunctionEnd + + +Function DetectJRE + ReadRegStr $2 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment" \ + "CurrentVersion" + StrCmp $2 ${JRE_VERSION} done + + StrCmp $2 ${JRE_ALTERNATE} done + + Call GetJRE + + done: +FunctionEnd + +; Pages to present + +Page license +Page components +Page directory +Page instfiles + +UninstPage uninstConfirm +UninstPage instfiles + +; And the stuff to install + +Section "Install Driver" InstDriver + + InstDrv::InitDriverSetup /NOUNLOAD {4D36E96D-E325-11CE-BFC1-08002BE10318} AltusMetrumSerial + Pop $0 + DetailPrint "InitDriverSetup: $0" + InstDrv::DeleteOemInfFiles /NOUNLOAD + InstDrv::CreateDevice /NOUNLOAD + + SetOutPath $TEMP + File "../telemetrum.inf" + InstDrv::InstallDriver /NOUNLOAD "$TEMP\telemetrum.inf" + + SetOutPath $INSTDIR + File "../telemetrum.inf" + + SetOutPath $WINDIR\Inf + File "../telemetrum.inf" + +SectionEnd + +Section "AltosUI Application" + Call DetectJRE + + SetOutPath $INSTDIR + + File "altosui-fat.jar" + File "cmudict04.jar" + File "cmulex.jar" + File "cmu_time_awb.jar" + File "cmutimelex.jar" + File "cmu_us_kal.jar" + File "en_us.jar" + File "freetts.jar" + File "jfreechart.jar" + File "jcommon.jar" + + File "*.dll" + + File "../icon/*.ico" + + CreateShortCut "$SMPROGRAMS\AltusMetrum.lnk" "$SYSDIR\javaw.exe" "-jar altosui-fat.jar" "$INSTDIR\altus-metrum.ico" +SectionEnd + +Section "AltosUI Desktop Shortcut" + CreateShortCut "$DESKTOP\AltusMetrum.lnk" "$INSTDIR\altosui-fat.jar" "" "$INSTDIR\altus-metrum.ico" +SectionEnd + +Section "TeleMetrum and TeleDongle Firmware" + + SetOutPath $INSTDIR + + File "../src/telemetrum-v1.0/telemetrum-v1.0-${VERSION}.ihx" + File "../src/telemetrum-v1.1/telemetrum-v1.1-${VERSION}.ihx" + File "../src/telemetrum-v1.2/telemetrum-v1.2-${VERSION}.ihx" + File "../src/telemini-v1.0/telemini-v1.0-${VERSION}.ihx" + File "../src/teledongle-v0.2/teledongle-v0.2-${VERSION}.ihx" + +SectionEnd + +Section "Documentation" + + SetOutPath $INSTDIR + + File "../doc/altusmetrum.pdf" + File "../doc/altos.pdf" + File "../doc/telemetry.pdf" + File "../doc/telemetrum-outline.pdf" + File "../doc/megametrum-outline.pdf" +SectionEnd + +Section "Uninstaller" + + ; Deal with the uninstaller + + SetOutPath $INSTDIR + + ; Write the install path to the registry + WriteRegStr HKLM SOFTWARE\AltusMetrum "Install_Dir" "$INSTDIR" + + ; Write the uninstall keys for windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "DisplayName" "Altus Metrum" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "NoModify" "1" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" "NoRepair" "1" + + WriteUninstaller "uninstall.exe" +SectionEnd + +Section "Uninstall" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\AltusMetrum" + DeleteRegKey HKLM "Software\AltusMetrum" + + Delete "$INSTDIR\*.*" + RMDir "$INSTDIR" + + ; Remove devices + InstDrv::InitDriverSetup /NOUNLOAD {4D36E96D-E325-11CE-BFC1-08002BE10318} AltusMetrumSerial + InstDrv::DeleteOemInfFiles /NOUNLOAD + InstDrv::RemoveAllDevices + + ; Remove shortcuts, if any + Delete "$SMPROGRAMS\AltusMetrum.lnk" + Delete "$DESKTOP\AltusMetrum.lnk" +SectionEnd diff --git a/altosui/altosui-fat b/altosui/altosui-fat new file mode 100755 index 00000000..95b1c051 --- /dev/null +++ b/altosui/altosui-fat @@ -0,0 +1,4 @@ +#!/bin/sh +me=`which "$0"` +dir=`dirname "$me"` +exec java -cp "$dir/*" -Djava.library.path="$dir" -jar "$dir"/altosui-fat.jar "$@" diff --git a/altosui/altosui.1 b/altosui/altosui.1 new file mode 100644 index 00000000..57fa4489 --- /dev/null +++ b/altosui/altosui.1 @@ -0,0 +1,46 @@ +.\" +.\" Copyright © 2010 Bdale Garbee <bdale@gag.com> +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, but +.\" WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +.\" General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License along +.\" with this program; if not, write to the Free Software Foundation, Inc., +.\" 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +.\" +.\" +.TH ALTOSUI 1 "altosui" "" +.SH NAME +altosui \- Rocket flight monitor +.SH SYNOPSIS +.B "altosui" +.SH DESCRIPTION +.I altosui +connects to a TeleDongle or TeleMetrum device through a USB serial device. +It provides a menu-oriented +user interface to monitor, record and review rocket flight data. +.SH USAGE +When connected to a TeleDongle device, altosui turns on the radio +receiver and listens for telemetry packets. It displays the received +telemetry data, and reports flight status via voice synthesis. All +received telemetry information is recorded to a file. +.P +When connected to a TeleMetrum device, altosui can be used to configure the +TeleMetrum, and to downloads the eeprom data and store it in a file. +.P +A number of other menu options exist, including the ability to export flight +data in different formats. +.SH FILES +All data log files are recorded into a user-specified directory +(default ~/TeleMetrum). Files are named using the current date, the serial +number of the reporting device, the flight number recorded in the data +and either '.telem' for telemetry data or '.eeprom' for eeprom data. +.SH AUTHOR +Keith Packard diff --git a/altosui/altusmetrum.jpg b/altosui/altusmetrum.jpg Binary files differnew file mode 100644 index 00000000..04027921 --- /dev/null +++ b/altosui/altusmetrum.jpg diff --git a/altosui/launch-sites.txt b/altosui/launch-sites.txt new file mode 100644 index 00000000..de7955e0 --- /dev/null +++ b/altosui/launch-sites.txt @@ -0,0 +1,13 @@ +AHPRA BALLS:40.808333333333333:-119.15 +ARS Rio Rancho:35.33475:-106.75361 +HARA Bragg Farms:34.895875:-86.616211 +KLOUDBusters Rocket Pasture:37.167833333:-97.73975 +METRA Pine Island:41.31939:-74.47077 +NCR Pawnee:40.885955:-104.63793 +OPROC Discovery Bay:47.97808,-122.896383 +OROC Brothers:43.79949786336483:-120.6485810681392 +OROC Sheridan:45.044176:-123.314323 +QRS Cedar Grove:-27.8512283:152.9624000 +SCORE Hudson Ranch:38.155602777777778:-104.809119444444444 +Tripoli Colorado Hartsel:39.009247:-105.702338 +WAC Sportsman Club:47.815327777777778:-119.427186111111111 diff --git a/altosui/libaltos/.gitignore b/altosui/libaltos/.gitignore new file mode 100644 index 00000000..c490e6f8 --- /dev/null +++ b/altosui/libaltos/.gitignore @@ -0,0 +1,12 @@ +*.so +*.lo +*.la +*.java +*.class +.libs/ +classlibaltos.stamp +libaltos_wrap.c +libaltosJNI +cjnitest +libaltos.swig +swig_bindings/ diff --git a/altosui/libaltos/Makefile-standalone b/altosui/libaltos/Makefile-standalone new file mode 100644 index 00000000..4e438050 --- /dev/null +++ b/altosui/libaltos/Makefile-standalone @@ -0,0 +1,126 @@ +OS:=$(shell uname) + +# +# Linux +# +ifeq ($(OS),Linux) + +JAVA_CFLAGS=-I/usr/lib/jvm/java-6-openjdk/include + +OS_LIB_CFLAGS=-DLINUX -DPOSIX_TTY $(JAVA_CFLAGS) + +OS_APP_CFLAGS=$(OS_LIB_CFLAGS) + +OS_LDFLAGS= + +LIBNAME=libaltos.so +EXEEXT= +endif + +# +# Darwin (Mac OS X) +# +ifeq ($(OS),Darwin) + +OS_LIB_CFLAGS=\ + -DDARWIN -DPOSIX_TTY -arch i386 -arch x86_64 \ + --sysroot=/Developer/SDKs/MacOSX10.5.sdk -mmacosx-version-min=10.5 \ + -iwithsysroot /System/Library/Frameworks/JavaVM.framework/Headers \ + -iwithsysroot /System/Library/Frameworks/IOKit.framework/Headers \ + -iwithsysroot /System/Library/Frameworks/CoreFoundation.framework/Headers +OS_APP_CFLAGS=$(OS_LIB_CFLAGS) + +OS_LDFLAGS =\ + -framework IOKit -framework CoreFoundation + +LIBNAME=libaltos.dylib +EXEEXT= + +endif + +# +# Windows +# +ifneq (,$(findstring MINGW,$(OS))) + +CC=gcc + +OS_LIB_CFLAGS = -DWINDOWS -mconsole -DBUILD_DLL +OS_APP_CFLAGS = -DWINDOWS -mconsole + +OS_LDFLAGS = -lgdi32 -luser32 -lcfgmgr32 -lsetupapi -lole32 \ + -ladvapi32 -lcomctl32 -mconsole -Wl,--add-stdcall-alias + +LIBNAME=altos.dll + +EXEEXT=.exe + +endif + +.SUFFIXES: .java .class + +CLASSPATH=".:jnitest/*:libaltosJNI:/usr/share/java/*" + +SWIG_DIR=swig_bindings/java +SWIG_FILE=$(SWIG_DIR)/libaltos.swig +SWIG_WRAP=$(SWIG_DIR)/libaltos_wrap.c + +JNI_DIR=libaltosJNI +JNI_FILE=$(JNI_DIR)/libaltosJNI.java +JNI_SRCS=$(JNI_FILE) \ + $(JNI_DIR)/SWIGTYPE_p_altos_file.java \ + $(JNI_DIR)/SWIGTYPE_p_altos_list.java \ + $(JNI_DIR)/altos_device.java \ + $(JNI_DIR)/libaltos.java + +JAVAFILES=\ + $(JNI_SRCS) + +CLASSFILES = $(JAVAFILES:%.java=%.class) + +JAVAFLAGS=-Xlint:unchecked + +CJNITEST=cjnitest$(EXEEXT) + +all: $(LIBNAME) $(CJNITEST) $(CLASSFILES) + +.java.class: + javac -encoding UTF8 -classpath "$(CLASSPATH)" $(JAVAFLAGS) $*.java + +CFLAGS=$(OS_LIB_CFLAGS) -O -I. + +LDFLAGS=$(OS_LDFLAGS) + +HEADERS=libaltos.h +SRCS = libaltos.c $(SWIG_WRAP) +OBJS = $(SRCS:%.c=%.o) +LIBS = $(DARWIN_LIBS) + +$(CJNITEST): cjnitest.c $(LIBNAME) + $(CC) -o $@ $(OS_APP_CFLAGS) cjnitest.c $(LIBNAME) $(LIBS) $(LDFLAGS) + +$(LIBNAME): $(OBJS) + $(CC) -shared $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(LDFLAGS) + +clean: + rm -f $(CLASSFILES) $(OBJS) $(LIBNAME) $(CJNITEST) cjnitest.o + rm -rf swig_bindings libaltosJNI + +distclean: clean + +$(JNI_FILE): libaltos.i0 $(HEADERS) + mkdir -p $(SWIG_DIR) + mkdir -p libaltosJNI + sed 's;//%;%;' libaltos.i0 $(HEADERS) > $(SWIG_FILE) + swig -java -package libaltosJNI $(SWIG_FILE) + cp swig_bindings/java/*.java libaltosJNI + +$(SWIG_WRAP): $(JNI_FILE) + +ifeq ($(OS),Linux) +install: $(LIBNAME) + install -c $(LIBNAME) $(DESTDIR)/usr/lib/altos/$(LIBNAME) + +endif + +.NOTPARALLEL: diff --git a/altosui/libaltos/Makefile.am b/altosui/libaltos/Makefile.am new file mode 100644 index 00000000..b5ab1ddb --- /dev/null +++ b/altosui/libaltos/Makefile.am @@ -0,0 +1,54 @@ +JAVAC=javac +AM_CFLAGS=-DLINUX -DPOSIX_TTY -I$(JVM_INCLUDE) +AM_JAVACFLAGS=-encoding UTF-8 + +altoslibdir=$(libdir)/altos + +altoslib_LTLIBRARIES=libaltos.la + +libaltos_la_LDFLAGS = -version-info 1:0:1 + +libaltos_la_SOURCES=\ + libaltos.c \ + libaltos_wrap.c + +noinst_PROGRAMS=cjnitest + +cjnitest_LDADD=libaltos.la + +LIBS=-lbluetooth + +HFILES=libaltos.h + +SWIG_FILE=libaltos.swig + +CLASSDIR=libaltosJNI + +$(SWIG_FILE): libaltos.i0 $(HFILES) + sed 's;//%;%;' libaltos.i0 $(HFILES) > $(SWIG_FILE) + +all-local: classlibaltos.stamp + +libaltos_wrap.c: classlibaltos.stamp + +classlibaltos.stamp: $(SWIG_FILE) + swig -java -package libaltosJNI $(SWIG_FILE) + mkdir -p libaltosJNI + $(JAVAC) -d . $(AM_JAVACFLAGS) $(JAVACFLAGS) *.java && \ + touch classlibaltos.stamp + +MINGCC32=i686-w64-mingw32-gcc +MINGCC64=x86_64-w64-mingw32-gcc +MINGFLAGS=-Wall -DWINDOWS -DBUILD_DLL -I$(JVM_INCLUDE) +MINGLIBS=-lsetupapi + +fat: altos.dll altos64.dll + +altos.dll: $(libaltos_la_SOURCES) + $(MINGCC32) -o $@ $(MINGFLAGS) -shared $(libaltos_la_SOURCES) $(MINGLIBS) + +altos64.dll: $(libaltos_la_SOURCES) + $(MINGCC64) -o $@ $(MINGFLAGS) -shared $(libaltos_la_SOURCES) $(MINGLIBS) + +clean-local: + -rm -rf libaltosJNI *.class *.java classlibaltos.stamp $(SWIG_FILE) libaltos_wrap.c altos.dll altos64.dll diff --git a/altosui/libaltos/cjnitest.c b/altosui/libaltos/cjnitest.c new file mode 100644 index 00000000..f0fe78f7 --- /dev/null +++ b/altosui/libaltos/cjnitest.c @@ -0,0 +1,71 @@ +#include <stdio.h> +#include "libaltos.h" + +static void +altos_puts(struct altos_file *file, char *string) +{ + char c; + + while ((c = *string++)) + altos_putchar(file, c); +} + +main () +{ + struct altos_device device; + struct altos_list *list; + struct altos_bt_device bt_device; + struct altos_bt_list *bt_list; + + altos_init(); + list = altos_list_start(); + while (altos_list_next(list, &device)) { + struct altos_file *file; + int c; + + printf ("%04x:%04x %-20s %4d %s\n", device.vendor, device.product, + device.name, device.serial, device.path); + + file = altos_open(&device); + if (!file) { + printf("altos_open failed\n"); + continue; + } + altos_puts(file,"v\nc s\n"); + altos_flush(file); + while ((c = altos_getchar(file, 100)) >= 0) { + putchar (c); + } + if (c != LIBALTOS_TIMEOUT) + printf ("getchar returns %d\n", c); + altos_close(file); + } + altos_list_finish(list); +#if HAS_BLUETOOTH + bt_list = altos_bt_list_start(8); + while (altos_bt_list_next(bt_list, &bt_device)) { + printf ("%s %s\n", bt_device.name, bt_device.addr); + if (strncmp(bt_device.name, "TeleBT", 6) == 0) { + struct altos_file *file; + + int c; + file = altos_bt_open(&bt_device); + if (!file) { + printf("altos_bt_open failed\n"); + continue; + } + altos_puts(file,"v\nc s\n"); + altos_flush(file); + while ((c = altos_getchar(file, 100)) >= 0) { + putchar(c); + } + if (c != LIBALTOS_TIMEOUT) + printf("getchar returns %d\n", c); + altos_close(file); + } + } + altos_bt_list_finish(bt_list); +#endif + altos_fini(); + return 0; +} diff --git a/altosui/libaltos/libaltos.c b/altosui/libaltos/libaltos.c new file mode 100644 index 00000000..515432f9 --- /dev/null +++ b/altosui/libaltos/libaltos.c @@ -0,0 +1,1318 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "libaltos.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define USB_VENDOR_FSF 0xfffe +#define USB_VENDOR_ALTUSMETRUM USB_VENDOR_FSF +#define USB_PRODUCT_ALTUSMETRUM 0x000a +#define USB_PRODUCT_ALTUSMETRUM_MIN 0x000a +#define USB_PRODUCT_ALTUSMETRUM_MAX 0x00ff + +#define USB_IS_ALTUSMETRUM(v,p) ((v) == USB_VENDOR_ALTUSMETRUM && \ + (USB_PRODUCT_ALTUSMETRUM_MIN <= (p) && \ + (p) <= USB_PRODUCT_ALTUSMETRUM_MAX)) + +#define BLUETOOTH_PRODUCT_TELEBT "TeleBT" + +#define USE_POLL + +PUBLIC int +altos_init(void) +{ + return LIBALTOS_SUCCESS; +} + +PUBLIC void +altos_fini(void) +{ +} + +static struct altos_error last_error; + +static void +altos_set_last_error(int code, char *string) +{ + last_error.code = code; + strncpy(last_error.string, string, sizeof (last_error.string) -1); + last_error.string[sizeof(last_error.string)-1] = '\0'; +} + +PUBLIC void +altos_get_last_error(struct altos_error *error) +{ + *error = last_error; +} + +#ifdef DARWIN + +#undef USE_POLL + +/* Mac OS X don't have strndup even if _GNU_SOURCE is defined */ +static char * +altos_strndup (const char *s, size_t n) +{ + size_t len = strlen (s); + char *ret; + + if (len <= n) + return strdup (s); + ret = malloc(n + 1); + strncpy(ret, s, n); + ret[n] = '\0'; + return ret; +} + +#else +#define altos_strndup strndup +#endif + +#ifdef POSIX_TTY + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <termios.h> +#include <errno.h> + +#define USB_BUF_SIZE 64 + +struct altos_file { + int fd; +#ifdef USE_POLL + int pipe[2]; +#else + int out_fd; +#endif + unsigned char out_data[USB_BUF_SIZE]; + int out_used; + unsigned char in_data[USB_BUF_SIZE]; + int in_used; + int in_read; +}; + +static void +altos_set_last_posix_error(void) +{ + altos_set_last_error(errno, strerror(errno)); +} + +PUBLIC struct altos_file * +altos_open(struct altos_device *device) +{ + struct altos_file *file = calloc (sizeof (struct altos_file), 1); + int ret; + struct termios term; + + if (!file) { + altos_set_last_posix_error(); + return NULL; + } + +// altos_set_last_error(12, "yeah yeah, failed again"); +// free(file); +// return NULL; + + file->fd = open(device->path, O_RDWR | O_NOCTTY); + if (file->fd < 0) { + altos_set_last_posix_error(); + free(file); + return NULL; + } +#ifdef USE_POLL + pipe(file->pipe); +#else + file->out_fd = open(device->path, O_RDWR | O_NOCTTY); + if (file->out_fd < 0) { + altos_set_last_posix_error(); + close(file->fd); + free(file); + return NULL; + } +#endif + ret = tcgetattr(file->fd, &term); + if (ret < 0) { + altos_set_last_posix_error(); + close(file->fd); +#ifndef USE_POLL + close(file->out_fd); +#endif + free(file); + return NULL; + } + cfmakeraw(&term); +#ifdef USE_POLL + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; +#else + term.c_cc[VMIN] = 0; + term.c_cc[VTIME] = 1; +#endif + ret = tcsetattr(file->fd, TCSAFLUSH, &term); + if (ret < 0) { + altos_set_last_posix_error(); + close(file->fd); +#ifndef USE_POLL + close(file->out_fd); +#endif + free(file); + return NULL; + } + return file; +} + +PUBLIC void +altos_close(struct altos_file *file) +{ + if (file->fd != -1) { + int fd = file->fd; + file->fd = -1; +#ifdef USE_POLL + write(file->pipe[1], "\r", 1); +#else + close(file->out_fd); + file->out_fd = -1; +#endif + close(fd); + } +} + +PUBLIC void +altos_free(struct altos_file *file) +{ + altos_close(file); + free(file); +} + +PUBLIC int +altos_flush(struct altos_file *file) +{ + if (file->out_used && 0) { + printf ("flush \""); + fwrite(file->out_data, 1, file->out_used, stdout); + printf ("\"\n"); + } + while (file->out_used) { + int ret; + + if (file->fd < 0) + return -EBADF; +#ifdef USE_POLL + ret = write (file->fd, file->out_data, file->out_used); +#else + ret = write (file->out_fd, file->out_data, file->out_used); +#endif + if (ret < 0) { + altos_set_last_posix_error(); + return -last_error.code; + } + if (ret) { + memmove(file->out_data, file->out_data + ret, + file->out_used - ret); + file->out_used -= ret; + } + } + return 0; +} + +PUBLIC int +altos_putchar(struct altos_file *file, char c) +{ + int ret; + + if (file->out_used == USB_BUF_SIZE) { + ret = altos_flush(file); + if (ret) { + return ret; + } + } + file->out_data[file->out_used++] = c; + ret = 0; + if (file->out_used == USB_BUF_SIZE) + ret = altos_flush(file); + return ret; +} + +#ifdef USE_POLL +#include <poll.h> +#endif + +static int +altos_fill(struct altos_file *file, int timeout) +{ + int ret; +#ifdef USE_POLL + struct pollfd fd[2]; +#endif + + if (timeout == 0) + timeout = -1; + while (file->in_read == file->in_used) { + if (file->fd < 0) + return LIBALTOS_ERROR; +#ifdef USE_POLL + fd[0].fd = file->fd; + fd[0].events = POLLIN|POLLERR|POLLHUP|POLLNVAL; + fd[1].fd = file->pipe[0]; + fd[1].events = POLLIN; + ret = poll(fd, 2, timeout); + if (ret < 0) { + altos_set_last_posix_error(); + return LIBALTOS_ERROR; + } + if (ret == 0) + return LIBALTOS_TIMEOUT; + + if (fd[0].revents & (POLLHUP|POLLERR|POLLNVAL)) + return LIBALTOS_ERROR; + if (fd[0].revents & POLLIN) +#endif + { + ret = read(file->fd, file->in_data, USB_BUF_SIZE); + if (ret < 0) { + altos_set_last_posix_error(); + return LIBALTOS_ERROR; + } + file->in_read = 0; + file->in_used = ret; +#ifndef USE_POLL + if (ret == 0 && timeout > 0) + return LIBALTOS_TIMEOUT; +#endif + } + } + if (file->in_used && 0) { + printf ("fill \""); + fwrite(file->in_data, 1, file->in_used, stdout); + printf ("\"\n"); + } + return 0; +} + +PUBLIC int +altos_getchar(struct altos_file *file, int timeout) +{ + int ret; + while (file->in_read == file->in_used) { + if (file->fd < 0) + return LIBALTOS_ERROR; + ret = altos_fill(file, timeout); + if (ret) + return ret; + } + return file->in_data[file->in_read++]; +} + +#endif /* POSIX_TTY */ + +/* + * Scan for Altus Metrum devices by looking through /sys + */ + +#ifdef LINUX + +#define _GNU_SOURCE +#include <ctype.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/rfcomm.h> + +static char * +cc_fullname (char *dir, char *file) +{ + char *new; + int dlen = strlen (dir); + int flen = strlen (file); + int slen = 0; + + if (dir[dlen-1] != '/') + slen = 1; + new = malloc (dlen + slen + flen + 1); + if (!new) + return 0; + strcpy(new, dir); + if (slen) + strcat (new, "/"); + strcat(new, file); + return new; +} + +static char * +cc_basename(char *file) +{ + char *b; + + b = strrchr(file, '/'); + if (!b) + return file; + return b + 1; +} + +static char * +load_string(char *dir, char *file) +{ + char *full = cc_fullname(dir, file); + char line[4096]; + char *r; + FILE *f; + int rlen; + + f = fopen(full, "r"); + free(full); + if (!f) + return NULL; + r = fgets(line, sizeof (line), f); + fclose(f); + if (!r) + return NULL; + rlen = strlen(r); + if (r[rlen-1] == '\n') + r[rlen-1] = '\0'; + return strdup(r); +} + +static int +load_hex(char *dir, char *file) +{ + char *line; + char *end; + long i; + + line = load_string(dir, file); + if (!line) + return -1; + i = strtol(line, &end, 16); + free(line); + if (end == line) + return -1; + return i; +} + +static int +load_dec(char *dir, char *file) +{ + char *line; + char *end; + long i; + + line = load_string(dir, file); + if (!line) + return -1; + i = strtol(line, &end, 10); + free(line); + if (end == line) + return -1; + return i; +} + +static int +dir_filter_tty_colon(const struct dirent *d) +{ + return strncmp(d->d_name, "tty:", 4) == 0; +} + +static int +dir_filter_tty(const struct dirent *d) +{ + return strncmp(d->d_name, "tty", 3) == 0; +} + +struct altos_usbdev { + char *sys; + char *tty; + char *manufacturer; + char *product_name; + int serial; /* AltOS always uses simple integer serial numbers */ + int idProduct; + int idVendor; +}; + +static char * +usb_tty(char *sys) +{ + char *base; + int num_configs; + int config; + struct dirent **namelist; + int interface; + int num_interfaces; + char endpoint_base[20]; + char *endpoint_full; + char *tty_dir; + int ntty; + char *tty; + + base = cc_basename(sys); + num_configs = load_hex(sys, "bNumConfigurations"); + num_interfaces = load_hex(sys, "bNumInterfaces"); + for (config = 1; config <= num_configs; config++) { + for (interface = 0; interface < num_interfaces; interface++) { + sprintf(endpoint_base, "%s:%d.%d", + base, config, interface); + endpoint_full = cc_fullname(sys, endpoint_base); + + /* Check for tty:ttyACMx style names + */ + ntty = scandir(endpoint_full, &namelist, + dir_filter_tty_colon, + alphasort); + if (ntty > 0) { + free(endpoint_full); + tty = cc_fullname("/dev", namelist[0]->d_name + 4); + free(namelist); + return tty; + } + + /* Check for tty/ttyACMx style names + */ + tty_dir = cc_fullname(endpoint_full, "tty"); + free(endpoint_full); + ntty = scandir(tty_dir, &namelist, + dir_filter_tty, + alphasort); + free (tty_dir); + if (ntty > 0) { + tty = cc_fullname("/dev", namelist[0]->d_name); + free(namelist); + return tty; + } + } + } + return NULL; +} + +static struct altos_usbdev * +usb_scan_device(char *sys) +{ + struct altos_usbdev *usbdev; + + usbdev = calloc(1, sizeof (struct altos_usbdev)); + if (!usbdev) + return NULL; + usbdev->sys = strdup(sys); + usbdev->manufacturer = load_string(sys, "manufacturer"); + usbdev->product_name = load_string(sys, "product"); + usbdev->serial = load_dec(sys, "serial"); + usbdev->idProduct = load_hex(sys, "idProduct"); + usbdev->idVendor = load_hex(sys, "idVendor"); + usbdev->tty = usb_tty(sys); + return usbdev; +} + +static void +usbdev_free(struct altos_usbdev *usbdev) +{ + free(usbdev->sys); + free(usbdev->manufacturer); + free(usbdev->product_name); + /* this can get used as a return value */ + if (usbdev->tty) + free(usbdev->tty); + free(usbdev); +} + +#define USB_DEVICES "/sys/bus/usb/devices" + +static int +dir_filter_dev(const struct dirent *d) +{ + const char *n = d->d_name; + char c; + + while ((c = *n++)) { + if (isdigit(c)) + continue; + if (c == '-') + continue; + if (c == '.' && n != d->d_name + 1) + continue; + return 0; + } + return 1; +} + +struct altos_list { + struct altos_usbdev **dev; + int current; + int ndev; +}; + +struct altos_list * +altos_list_start(void) +{ + int e; + struct dirent **ents; + char *dir; + struct altos_usbdev *dev; + struct altos_list *devs; + int n; + + devs = calloc(1, sizeof (struct altos_list)); + if (!devs) + return NULL; + + n = scandir (USB_DEVICES, &ents, + dir_filter_dev, + alphasort); + if (!n) + return 0; + for (e = 0; e < n; e++) { + dir = cc_fullname(USB_DEVICES, ents[e]->d_name); + dev = usb_scan_device(dir); + free(dir); + if (USB_IS_ALTUSMETRUM(dev->idVendor, dev->idProduct)) { + if (devs->dev) + devs->dev = realloc(devs->dev, + (devs->ndev + 1) * sizeof (struct usbdev *)); + else + devs->dev = malloc (sizeof (struct usbdev *)); + devs->dev[devs->ndev++] = dev; + } + } + free(ents); + devs->current = 0; + return devs; +} + +int +altos_list_next(struct altos_list *list, struct altos_device *device) +{ + struct altos_usbdev *dev; + if (list->current >= list->ndev) + return 0; + dev = list->dev[list->current]; + strcpy(device->name, dev->product_name); + device->vendor = dev->idVendor; + device->product = dev->idProduct; + strcpy(device->path, dev->tty); + device->serial = dev->serial; + list->current++; + return 1; +} + +void +altos_list_finish(struct altos_list *usbdevs) +{ + int i; + + if (!usbdevs) + return; + for (i = 0; i < usbdevs->ndev; i++) + usbdev_free(usbdevs->dev[i]); + free(usbdevs); +} + +struct altos_bt_list { + inquiry_info *ii; + int sock; + int dev_id; + int rsp; + int num_rsp; +}; + +#define INQUIRY_MAX_RSP 255 + +struct altos_bt_list * +altos_bt_list_start(int inquiry_time) +{ + struct altos_bt_list *bt_list; + + bt_list = calloc(1, sizeof (struct altos_bt_list)); + if (!bt_list) + goto no_bt_list; + + bt_list->ii = calloc(INQUIRY_MAX_RSP, sizeof (inquiry_info)); + if (!bt_list->ii) + goto no_ii; + bt_list->dev_id = hci_get_route(NULL); + if (bt_list->dev_id < 0) + goto no_dev_id; + + bt_list->sock = hci_open_dev(bt_list->dev_id); + if (bt_list->sock < 0) + goto no_sock; + + bt_list->num_rsp = hci_inquiry(bt_list->dev_id, + inquiry_time, + INQUIRY_MAX_RSP, + NULL, + &bt_list->ii, + IREQ_CACHE_FLUSH); + if (bt_list->num_rsp < 0) + goto no_rsp; + + bt_list->rsp = 0; + return bt_list; + +no_rsp: + close(bt_list->sock); +no_sock: +no_dev_id: + free(bt_list->ii); +no_ii: + free(bt_list); +no_bt_list: + return NULL; +} + +int +altos_bt_list_next(struct altos_bt_list *bt_list, + struct altos_bt_device *device) +{ + inquiry_info *ii; + + if (bt_list->rsp >= bt_list->num_rsp) + return 0; + + ii = &bt_list->ii[bt_list->rsp]; + ba2str(&ii->bdaddr, device->addr); + memset(&device->name, '\0', sizeof (device->name)); + if (hci_read_remote_name(bt_list->sock, &ii->bdaddr, + sizeof (device->name), + device->name, 0) < 0) { + strcpy(device->name, "[unknown]"); + } + bt_list->rsp++; + return 1; +} + +void +altos_bt_list_finish(struct altos_bt_list *bt_list) +{ + close(bt_list->sock); + free(bt_list->ii); + free(bt_list); +} + +void +altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device) +{ + strncpy(device->name, name, sizeof (device->name)); + device->name[sizeof(device->name)-1] = '\0'; + strncpy(device->addr, addr, sizeof (device->addr)); + device->addr[sizeof(device->addr)-1] = '\0'; +} + +struct altos_file * +altos_bt_open(struct altos_bt_device *device) +{ + struct sockaddr_rc addr = { 0 }; + int s, status; + struct altos_file *file; + + file = calloc(1, sizeof (struct altos_file)); + if (!file) + goto no_file; + file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (file->fd < 0) { + altos_set_last_posix_error(); + goto no_sock; + } + + addr.rc_family = AF_BLUETOOTH; + addr.rc_channel = 1; + str2ba(device->addr, &addr.rc_bdaddr); + + status = connect(file->fd, + (struct sockaddr *)&addr, + sizeof(addr)); + if (status < 0) { + altos_set_last_posix_error(); + goto no_link; + } + sleep(1); + +#ifdef USE_POLL + pipe(file->pipe); +#else + file->out_fd = dup(file->fd); +#endif + return file; +no_link: + close(s); +no_sock: + free(file); +no_file: + return NULL; +} + +#endif + +#ifdef DARWIN + +#include <IOKitLib.h> +#include <IOKit/usb/USBspec.h> +#include <sys/param.h> +#include <paths.h> +#include <CFNumber.h> +#include <IOBSD.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +struct altos_list { + io_iterator_t iterator; +}; + +static int +get_string(io_object_t object, CFStringRef entry, char *result, int result_len) +{ + CFTypeRef entry_as_string; + Boolean got_string; + + entry_as_string = IORegistryEntrySearchCFProperty (object, + kIOServicePlane, + entry, + kCFAllocatorDefault, + kIORegistryIterateRecursively); + if (entry_as_string) { + got_string = CFStringGetCString(entry_as_string, + result, result_len, + kCFStringEncodingASCII); + + CFRelease(entry_as_string); + if (got_string) + return 1; + } + return 0; +} + +static int +get_number(io_object_t object, CFStringRef entry, int *result) +{ + CFTypeRef entry_as_number; + Boolean got_number; + + entry_as_number = IORegistryEntrySearchCFProperty (object, + kIOServicePlane, + entry, + kCFAllocatorDefault, + kIORegistryIterateRecursively); + if (entry_as_number) { + got_number = CFNumberGetValue(entry_as_number, + kCFNumberIntType, + result); + if (got_number) + return 1; + } + return 0; +} + +PUBLIC struct altos_list * +altos_list_start(void) +{ + struct altos_list *list = calloc (sizeof (struct altos_list), 1); + CFMutableDictionaryRef matching_dictionary = IOServiceMatching("IOUSBDevice"); + io_iterator_t tdIterator; + io_object_t tdObject; + kern_return_t ret; + int i; + + ret = IOServiceGetMatchingServices(kIOMasterPortDefault, matching_dictionary, &list->iterator); + if (ret != kIOReturnSuccess) + return NULL; + return list; +} + +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device) +{ + io_object_t object; + char serial_string[128]; + + for (;;) { + object = IOIteratorNext(list->iterator); + if (!object) + return 0; + + if (!get_number (object, CFSTR(kUSBVendorID), &device->vendor) || + !get_number (object, CFSTR(kUSBProductID), &device->product)) + continue; + if (device->vendor != 0xfffe) + continue; + if (device->product < 0x000a || 0x0013 < device->product) + continue; + if (get_string (object, CFSTR("IOCalloutDevice"), device->path, sizeof (device->path)) && + get_string (object, CFSTR("USB Product Name"), device->name, sizeof (device->name)) && + get_string (object, CFSTR("USB Serial Number"), serial_string, sizeof (serial_string))) { + device->serial = atoi(serial_string); + return 1; + } + } +} + +PUBLIC void +altos_list_finish(struct altos_list *list) +{ + IOObjectRelease (list->iterator); + free(list); +} + +struct altos_bt_list { + int sock; + int dev_id; + int rsp; + int num_rsp; +}; + +#define INQUIRY_MAX_RSP 255 + +struct altos_bt_list * +altos_bt_list_start(int inquiry_time) +{ + return NULL; +} + +int +altos_bt_list_next(struct altos_bt_list *bt_list, + struct altos_bt_device *device) +{ + return 0; +} + +void +altos_bt_list_finish(struct altos_bt_list *bt_list) +{ +} + +void +altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device) +{ + strncpy(device->name, name, sizeof (device->name)); + device->name[sizeof(device->name)-1] = '\0'; + strncpy(device->addr, addr, sizeof (device->addr)); + device->addr[sizeof(device->addr)-1] = '\0'; +} + +struct altos_file * +altos_bt_open(struct altos_bt_device *device) +{ + return NULL; +} + +#endif + + +#ifdef WINDOWS + +#include <stdlib.h> +#include <windows.h> +#include <setupapi.h> + +struct altos_list { + HDEVINFO dev_info; + int index; +}; + +#define USB_BUF_SIZE 64 + +struct altos_file { + HANDLE handle; + unsigned char out_data[USB_BUF_SIZE]; + int out_used; + unsigned char in_data[USB_BUF_SIZE]; + int in_used; + int in_read; + OVERLAPPED ov_read; + BOOL pend_read; + OVERLAPPED ov_write; +}; + +static void +altos_set_last_windows_error(void) +{ + DWORD error = GetLastError(); + TCHAR message[1024]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + 0, + error, + 0, + message, + sizeof (message) / sizeof (TCHAR), + NULL); + altos_set_last_error(error, message); +} + +PUBLIC struct altos_list * +altos_list_start(void) +{ + struct altos_list *list = calloc(1, sizeof (struct altos_list)); + + if (!list) + return NULL; + list->dev_info = SetupDiGetClassDevs(NULL, "USB", NULL, + DIGCF_ALLCLASSES|DIGCF_PRESENT); + if (list->dev_info == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + free(list); + return NULL; + } + list->index = 0; + return list; +} + +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device) +{ + SP_DEVINFO_DATA dev_info_data; + BYTE port[128]; + DWORD port_len; + char friendlyname[256]; + BYTE symbolic[256]; + DWORD symbolic_len; + HKEY dev_key; + unsigned int vid, pid; + int serial; + HRESULT result; + DWORD friendlyname_type; + DWORD friendlyname_len; + + dev_info_data.cbSize = sizeof (SP_DEVINFO_DATA); + while(SetupDiEnumDeviceInfo(list->dev_info, list->index, + &dev_info_data)) + { + list->index++; + + dev_key = SetupDiOpenDevRegKey(list->dev_info, &dev_info_data, + DICS_FLAG_GLOBAL, 0, DIREG_DEV, + KEY_READ); + if (dev_key == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + printf("cannot open device registry key\n"); + continue; + } + + /* Fetch symbolic name for this device and parse out + * the vid/pid/serial info */ + symbolic_len = sizeof(symbolic); + result = RegQueryValueEx(dev_key, "SymbolicName", NULL, NULL, + symbolic, &symbolic_len); + if (result != 0) { + altos_set_last_windows_error(); + printf("cannot find SymbolicName value\n"); + RegCloseKey(dev_key); + continue; + } + vid = pid = serial = 0; + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_") - 1, + "%04X", &vid); + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1, + "%04X", &pid); + sscanf((char *) symbolic + sizeof("\\??\\USB#VID_XXXX&PID_XXXX#") - 1, + "%d", &serial); + if (!USB_IS_ALTUSMETRUM(vid, pid)) { + RegCloseKey(dev_key); + continue; + } + + /* Fetch the com port name */ + port_len = sizeof (port); + result = RegQueryValueEx(dev_key, "PortName", NULL, NULL, + port, &port_len); + RegCloseKey(dev_key); + if (result != 0) { + altos_set_last_windows_error(); + printf("failed to get PortName\n"); + continue; + } + + /* Fetch the device description which is the device name, + * with firmware that has unique USB ids */ + friendlyname_len = sizeof (friendlyname); + if(!SetupDiGetDeviceRegistryProperty(list->dev_info, + &dev_info_data, + SPDRP_FRIENDLYNAME, + &friendlyname_type, + (BYTE *)friendlyname, + sizeof(friendlyname), + &friendlyname_len)) + { + altos_set_last_windows_error(); + printf("Failed to get friendlyname\n"); + continue; + } + device->vendor = vid; + device->product = pid; + device->serial = serial; + strcpy(device->name, friendlyname); + + strcpy(device->path, (char *) port); + return 1; + } + result = GetLastError(); + if (result != ERROR_NO_MORE_ITEMS) { + altos_set_last_windows_error(); + printf ("SetupDiEnumDeviceInfo failed error %d\n", (int) result); + } + return 0; +} + +PUBLIC void +altos_list_finish(struct altos_list *list) +{ + SetupDiDestroyDeviceInfoList(list->dev_info); + free(list); +} + +static int +altos_queue_read(struct altos_file *file) +{ + DWORD got; + if (file->pend_read) + return LIBALTOS_SUCCESS; + + if (!ReadFile(file->handle, file->in_data, USB_BUF_SIZE, &got, &file->ov_read)) { + if (GetLastError() != ERROR_IO_PENDING) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + file->pend_read = TRUE; + } else { + file->pend_read = FALSE; + file->in_read = 0; + file->in_used = got; + } + return LIBALTOS_SUCCESS; +} + +static int +altos_wait_read(struct altos_file *file, int timeout) +{ + DWORD ret; + DWORD got; + + if (!file->pend_read) + return LIBALTOS_SUCCESS; + + if (!timeout) + timeout = INFINITE; + + ret = WaitForSingleObject(file->ov_read.hEvent, timeout); + switch (ret) { + case WAIT_OBJECT_0: + if (!GetOverlappedResult(file->handle, &file->ov_read, &got, FALSE)) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + file->pend_read = FALSE; + file->in_read = 0; + file->in_used = got; + break; + case WAIT_TIMEOUT: + return LIBALTOS_TIMEOUT; + break; + default: + return LIBALTOS_ERROR; + } + return LIBALTOS_SUCCESS; +} + +static int +altos_fill(struct altos_file *file, int timeout) +{ + int ret; + + if (file->in_read < file->in_used) + return LIBALTOS_SUCCESS; + + file->in_read = file->in_used = 0; + + ret = altos_queue_read(file); + if (ret) + return ret; + ret = altos_wait_read(file, timeout); + if (ret) + return ret; + + return LIBALTOS_SUCCESS; +} + +PUBLIC int +altos_flush(struct altos_file *file) +{ + DWORD put; + unsigned char *data = file->out_data; + int used = file->out_used; + DWORD ret; + + while (used) { + if (!WriteFile(file->handle, data, used, &put, &file->ov_write)) { + if (GetLastError() != ERROR_IO_PENDING) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + ret = WaitForSingleObject(file->ov_write.hEvent, INFINITE); + switch (ret) { + case WAIT_OBJECT_0: + if (!GetOverlappedResult(file->handle, &file->ov_write, &put, FALSE)) { + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + break; + default: + altos_set_last_windows_error(); + return LIBALTOS_ERROR; + } + } + data += put; + used -= put; + } + file->out_used = 0; + return LIBALTOS_SUCCESS; +} + +PUBLIC struct altos_file * +altos_open(struct altos_device *device) +{ + struct altos_file *file = calloc (1, sizeof (struct altos_file)); + char full_name[64]; + DCB dcbSerialParams = {0}; + COMMTIMEOUTS timeouts; + + if (!file) + return NULL; + + strcpy(full_name, "\\\\.\\"); + strcat(full_name, device->path); + file->handle = CreateFile(full_name, GENERIC_READ|GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + if (file->handle == INVALID_HANDLE_VALUE) { + altos_set_last_windows_error(); + free(file); + return NULL; + } + file->ov_read.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + file->ov_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; + timeouts.ReadTotalTimeoutConstant = 1 << 30; /* almost forever */ + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + SetCommTimeouts(file->handle, &timeouts); + + dcbSerialParams.DCBlength = sizeof(dcbSerialParams); + if (!GetCommState(file->handle, &dcbSerialParams)) { + altos_set_last_windows_error(); + CloseHandle(file->handle); + free(file); + return NULL; + } + dcbSerialParams.BaudRate = CBR_9600; + dcbSerialParams.ByteSize = 8; + dcbSerialParams.StopBits = ONESTOPBIT; + dcbSerialParams.Parity = NOPARITY; + if (!SetCommState(file->handle, &dcbSerialParams)) { + altos_set_last_windows_error(); + CloseHandle(file->handle); + free(file); + return NULL; + } + + return file; +} + +PUBLIC void +altos_close(struct altos_file *file) +{ + if (file->handle != INVALID_HANDLE_VALUE) { + CloseHandle(file->handle); + file->handle = INVALID_HANDLE_VALUE; + } +} + +PUBLIC void +altos_free(struct altos_file *file) +{ + altos_close(file); + free(file); +} + +PUBLIC int +altos_putchar(struct altos_file *file, char c) +{ + int ret; + + if (file->out_used == USB_BUF_SIZE) { + ret = altos_flush(file); + if (ret) + return ret; + } + file->out_data[file->out_used++] = c; + if (file->out_used == USB_BUF_SIZE) + return altos_flush(file); + return LIBALTOS_SUCCESS; +} + +PUBLIC int +altos_getchar(struct altos_file *file, int timeout) +{ + int ret; + while (file->in_read == file->in_used) { + if (file->handle == INVALID_HANDLE_VALUE) + return LIBALTOS_ERROR; + ret = altos_fill(file, timeout); + if (ret) + return ret; + } + return file->in_data[file->in_read++]; +} + +struct altos_bt_list * +altos_bt_list_start(int inquiry_time) +{ + return NULL; +} + +int +altos_bt_list_next(struct altos_bt_list *bt_list, + struct altos_bt_device *device) +{ + return 0; +} + +void +altos_bt_list_finish(struct altos_bt_list *bt_list) +{ + free(bt_list); +} + +void +altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device) +{ + strncpy(device->name, name, sizeof (device->name)); + device->name[sizeof(device->name)-1] = '\0'; + strncpy(device->addr, addr, sizeof (device->addr)); + device->addr[sizeof(device->addr)-1] = '\0'; +} + +struct altos_file * +altos_bt_open(struct altos_bt_device *device) +{ + return NULL; +} + +#endif diff --git a/altosui/libaltos/libaltos.dylib b/altosui/libaltos/libaltos.dylib Binary files differnew file mode 100755 index 00000000..1038817d --- /dev/null +++ b/altosui/libaltos/libaltos.dylib diff --git a/altosui/libaltos/libaltos.h b/altosui/libaltos/libaltos.h new file mode 100644 index 00000000..f90fbb87 --- /dev/null +++ b/altosui/libaltos/libaltos.h @@ -0,0 +1,119 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _LIBALTOS_H_ +#define _LIBALTOS_H_ + +#include <stdlib.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) +# ifndef BUILD_STATIC +# ifdef BUILD_DLL +# define PUBLIC __declspec(dllexport) +# else +# define PUBLIC __declspec(dllimport) +# endif +# endif /* BUILD_STATIC */ +#endif + +#ifndef PUBLIC +# define PUBLIC +#endif + +struct altos_device { + //%immutable; + int vendor; + int product; + int serial; + char name[256]; + char path[256]; + //%mutable; +}; + +struct altos_bt_device { + //%immutable; + char name[256]; + char addr[20]; + //%mutable; +}; + +struct altos_error { + int code; + char string[1024]; +}; + +#define LIBALTOS_SUCCESS 0 +#define LIBALTOS_ERROR -1 +#define LIBALTOS_TIMEOUT -2 + +/* Returns 0 for success, < 0 on error */ +PUBLIC int +altos_init(void); + +PUBLIC void +altos_fini(void); + +PUBLIC void +altos_get_last_error(struct altos_error *error); + +PUBLIC struct altos_list * +altos_list_start(void); + +/* Returns 1 for success, zero on end of list */ +PUBLIC int +altos_list_next(struct altos_list *list, struct altos_device *device); + +PUBLIC void +altos_list_finish(struct altos_list *list); + +PUBLIC struct altos_file * +altos_open(struct altos_device *device); + +PUBLIC void +altos_close(struct altos_file *file); + +PUBLIC void +altos_free(struct altos_file *file); + +/* Returns < 0 for error */ +PUBLIC int +altos_putchar(struct altos_file *file, char c); + +/* Returns < 0 for error */ +PUBLIC int +altos_flush(struct altos_file *file); + +/* Returns < 0 for error or timeout. timeout of 0 == wait forever */ +PUBLIC int +altos_getchar(struct altos_file *file, int timeout); + +PUBLIC struct altos_bt_list * +altos_bt_list_start(int inquiry_time); + +PUBLIC int +altos_bt_list_next(struct altos_bt_list *list, struct altos_bt_device *device); + +PUBLIC void +altos_bt_list_finish(struct altos_bt_list *list); + +PUBLIC void +altos_bt_fill_in(char *name, char *addr, struct altos_bt_device *device); + +PUBLIC struct altos_file * +altos_bt_open(struct altos_bt_device *device); + +#endif /* _LIBALTOS_H_ */ diff --git a/altosui/libaltos/libaltos.i0 b/altosui/libaltos/libaltos.i0 new file mode 100644 index 00000000..d06468f5 --- /dev/null +++ b/altosui/libaltos/libaltos.i0 @@ -0,0 +1,5 @@ +%module libaltos +%{ +#include "libaltos.h" +%} + |
