diff options
author | Keith Packard <keithp@keithp.com> | 2010-11-24 14:57:57 -0800 |
---|---|---|
committer | Keith Packard <keithp@keithp.com> | 2010-11-24 15:09:05 -0800 |
commit | 3fbefb3eea981d34a09496cf8abf0119de2e35bf (patch) | |
tree | 6e7956ca5c9336c7c61ebdcede5336a4c0191c37 /altosui | |
parent | 7a50837ea0d92db3f469f197ec8210aee22aa143 (diff) |
Move altosui to the top level, placing libaltos inside it.
Signed-off-by: Keith Packard <keithp@keithp.com>
Diffstat (limited to 'altosui')
101 files changed, 14566 insertions, 0 deletions
diff --git a/altosui/.gitignore b/altosui/.gitignore new file mode 100644 index 00000000..89be1d53 --- /dev/null +++ b/altosui/.gitignore @@ -0,0 +1,19 @@ +windows/ +linux/ +macosx/ +fat/ +Manifest.txt +Manifest-fat.txt +libaltosJNI +classes +altosui +altosui-test +classaltosui.stamp +Altos-Linux-*.tar.bz2 +Altos-Mac-*.zip +Altos-Windows-*.exe +*.dll +*.dylib +*.so +*.jar +*.class diff --git a/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml b/altosui/AltOS Package Configuration.pmdoc/01altosui-contents.xml new file mode 100644 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..8ee94e04 --- /dev/null +++ b/altosui/Altos.java @@ -0,0 +1,218 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.util.*; +import java.text.*; + +public class Altos { + /* EEProm command letters */ + static final int AO_LOG_FLIGHT = 'F'; + static final int AO_LOG_SENSOR = 'A'; + static final int AO_LOG_TEMP_VOLT = 'T'; + static final int AO_LOG_DEPLOY = 'D'; + static final int AO_LOG_STATE = 'S'; + static final int AO_LOG_GPS_TIME = 'G'; + static final int AO_LOG_GPS_LAT = 'N'; + static final int AO_LOG_GPS_LON = 'W'; + static final int AO_LOG_GPS_ALT = 'H'; + static final int AO_LOG_GPS_SAT = 'V'; + static final int AO_LOG_GPS_DATE = 'Y'; + + /* Added for header fields in eeprom files */ + static final int AO_LOG_CONFIG_VERSION = 1000; + static final int AO_LOG_MAIN_DEPLOY = 1001; + static final int AO_LOG_APOGEE_DELAY = 1002; + static final int AO_LOG_RADIO_CHANNEL = 1003; + static final int AO_LOG_CALLSIGN = 1004; + static final int AO_LOG_ACCEL_CAL = 1005; + static final int AO_LOG_RADIO_CAL = 1006; + static final int AO_LOG_MANUFACTURER = 1007; + static final int AO_LOG_PRODUCT = 1008; + static final int AO_LOG_SERIAL_NUMBER = 1009; + static final int AO_LOG_SOFTWARE_VERSION = 1010; + + /* Added to flag invalid records */ + static final int AO_LOG_INVALID = -1; + + /* Flight state numbers and names */ + static final int ao_flight_startup = 0; + static final int ao_flight_idle = 1; + static final int ao_flight_pad = 2; + static final int ao_flight_boost = 3; + static final int ao_flight_fast = 4; + static final int ao_flight_coast = 5; + static final int ao_flight_drogue = 6; + static final int ao_flight_main = 7; + static final int ao_flight_landed = 8; + static final int ao_flight_invalid = 9; + + static HashMap<String,Integer> string_to_state = new HashMap<String,Integer>(); + + static boolean map_initialized = false; + + static final int tab_elt_pad = 5; + + static final Font label_font = new Font("Dialog", Font.PLAIN, 22); + static final Font value_font = new Font("Monospaced", Font.PLAIN, 22); + static final Font status_font = new Font("SansSerif", Font.BOLD, 24); + + static final int text_width = 16; + + static void initialize_map() + { + string_to_state.put("startup", ao_flight_startup); + string_to_state.put("idle", ao_flight_idle); + string_to_state.put("pad", ao_flight_pad); + string_to_state.put("boost", ao_flight_boost); + string_to_state.put("fast", ao_flight_fast); + string_to_state.put("coast", ao_flight_coast); + string_to_state.put("drogue", ao_flight_drogue); + string_to_state.put("main", ao_flight_main); + string_to_state.put("landed", ao_flight_landed); + string_to_state.put("invalid", ao_flight_invalid); + map_initialized = true; + } + + static String[] state_to_string = { + "startup", + "idle", + "pad", + "boost", + "fast", + "coast", + "drogue", + "main", + "landed", + "invalid", + }; + + static public int state(String state) { + if (!map_initialized) + initialize_map(); + if (string_to_state.containsKey(state)) + return string_to_state.get(state); + return ao_flight_invalid; + } + + static public String state_name(int state) { + if (state < 0 || state_to_string.length <= state) + return "invalid"; + return state_to_string[state]; + } + + static final int AO_GPS_VALID = (1 << 4); + static final int AO_GPS_RUNNING = (1 << 5); + static final int AO_GPS_DATE_VALID = (1 << 6); + static final int AO_GPS_NUM_SAT_SHIFT = 0; + static final int AO_GPS_NUM_SAT_MASK = 0xf; + + static boolean isspace(int c) { + switch (c) { + case ' ': + case '\t': + return true; + } + return false; + } + + static boolean ishex(int c) { + if ('0' <= c && c <= '9') + return true; + if ('a' <= c && c <= 'f') + return true; + if ('A' <= c && c <= 'F') + return true; + return false; + } + + static boolean ishex(String s) { + for (int i = 0; i < s.length(); i++) + if (!ishex(s.charAt(i))) + return false; + return true; + } + + static int fromhex(int c) { + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + return -1; + } + + static int fromhex(String s) throws NumberFormatException { + int c, v = 0; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (!ishex(c)) { + if (i == 0) + throw new NumberFormatException(String.format("invalid hex \"%s\"", s)); + return v; + } + v = v * 16 + fromhex(c); + } + return v; + } + + static boolean isdec(int c) { + if ('0' <= c && c <= '9') + return true; + return false; + } + + static boolean isdec(String s) { + for (int i = 0; i < s.length(); i++) + if (!isdec(s.charAt(i))) + return false; + return true; + } + + static int fromdec(int c) { + if ('0' <= c && c <= '9') + return c - '0'; + return -1; + } + + static int fromdec(String s) throws NumberFormatException { + int c, v = 0; + int sign = 1; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (i == 0 && c == '-') { + sign = -1; + } else if (!isdec(c)) { + if (i == 0) + throw new NumberFormatException(String.format("invalid number \"%s\"", s)); + return v; + } else + v = v * 10 + fromdec(c); + } + return v * sign; + } + + static String replace_extension(String input, String extension) { + int dot = input.lastIndexOf("."); + if (dot > 0) + input = input.substring(0,dot); + return input.concat(extension); + } +} diff --git a/altosui/AltosAscent.java b/altosui/AltosAscent.java new file mode 100644 index 00000000..64bdcf30 --- /dev/null +++ b/altosui/AltosAscent.java @@ -0,0 +1,335 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosAscent extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public class AscentStatus { + JLabel label; + JTextField value; + AltosLights lights; + + void show(AltosState state, int crc_errors) {} + void reset() { + value.setText(""); + lights.set(false); + } + + public AscentStatus (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + lights = new AltosLights(); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(lights, c); + add(lights); + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.gridwidth = 2; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + } + } + + public class AscentValue { + JLabel label; + JTextField value; + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + public AscentValue (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.gridwidth = 2; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + } + } + + public class AscentValueHold { + JLabel label; + JTextField value; + JTextField max_value; + double max; + + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + max_value.setText(""); + max = 0; + } + + void show(String format, double v) { + value.setText(String.format(format, v)); + if (v > max) { + max_value.setText(String.format(format, v)); + max = v; + } + } + public AscentValueHold (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 2; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + max_value = new JTextField(Altos.text_width); + max_value.setFont(Altos.value_font); + max_value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 3; c.gridy = y; + c.anchor = GridBagConstraints.EAST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(max_value, c); + add(max_value); + } + } + + + class Height extends AscentValueHold { + void show (AltosState state, int crc_errors) { + show("%6.0f m", state.height); + } + public Height (GridBagLayout layout, int y) { + super (layout, y, "Height"); + } + } + + Height height; + + class Speed extends AscentValueHold { + void show (AltosState state, int crc_errors) { + double speed = state.speed; + if (!state.ascent) + speed = state.baro_speed; + show("%6.0f m/s", speed); + } + public Speed (GridBagLayout layout, int y) { + super (layout, y, "Speed"); + } + } + + Speed speed; + + class Accel extends AscentValueHold { + void show (AltosState state, int crc_errors) { + show("%6.0f m/s²", state.acceleration); + } + public Accel (GridBagLayout layout, int y) { + super (layout, y, "Acceleration"); + } + } + + Accel accel; + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %4d° %9.6f", h, deg, min); + } + + class Apogee extends AscentStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.2f V", state.drogue_sense)); + lights.set(state.drogue_sense > 3.2); + } + public Apogee (GridBagLayout layout, int y) { + super(layout, y, "Apogee Igniter Voltage"); + } + } + + Apogee apogee; + + class Main extends AscentStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.2f V", state.main_sense)); + lights.set(state.main_sense > 3.2); + } + public Main (GridBagLayout layout, int y) { + super(layout, y, "Main Igniter Voltage"); + } + } + + Main main; + + class Lat extends AscentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null) + value.setText(pos(state.gps.lat,"N", "S")); + else + value.setText("???"); + } + public Lat (GridBagLayout layout, int y) { + super (layout, y, "Latitude"); + } + } + + Lat lat; + + class Lon extends AscentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null) + value.setText(pos(state.gps.lon,"E", "W")); + else + value.setText("???"); + } + public Lon (GridBagLayout layout, int y) { + super (layout, y, "Longitude"); + } + } + + Lon lon; + + public void reset() { + lat.reset(); + lon.reset(); + main.reset(); + apogee.reset(); + height.reset(); + speed.reset(); + accel.reset(); + } + + public void show(AltosState state, int crc_errors) { + lat.show(state, crc_errors); + lon.show(state, crc_errors); + height.show(state, crc_errors); + main.show(state, crc_errors); + apogee.show(state, crc_errors); + speed.show(state, crc_errors); + accel.show(state, crc_errors); + } + + public void labels(GridBagLayout layout, int y) { + GridBagConstraints c; + JLabel cur, max; + + cur = new JLabel("Current"); + cur.setFont(Altos.label_font); + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + layout.setConstraints(cur, c); + add(cur); + + max = new JLabel("Maximum"); + max.setFont(Altos.label_font); + c.gridx = 3; c.gridy = y; + layout.setConstraints(max, c); + add(max); + } + + public AltosAscent() { + layout = new GridBagLayout(); + + setLayout(layout); + + /* Elements in ascent display: + * + * lat + * lon + * height + */ + labels(layout, 0); + height = new Height(layout, 1); + speed = new Speed(layout, 2); + accel = new Accel(layout, 3); + lat = new Lat(layout, 4); + lon = new Lon(layout, 5); + apogee = new Apogee(layout, 6); + main = new Main(layout, 7); + } +} diff --git a/altosui/AltosCRCException.java b/altosui/AltosCRCException.java new file mode 100644 index 00000000..4a529bcf --- /dev/null +++ b/altosui/AltosCRCException.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 AltosCRCException extends Exception { + public int rssi; + + public AltosCRCException (int in_rssi) { + rssi = in_rssi; + } +} diff --git a/altosui/AltosCSV.java b/altosui/AltosCSV.java new file mode 100644 index 00000000..df98b2b4 --- /dev/null +++ b/altosui/AltosCSV.java @@ -0,0 +1,252 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; + +public class AltosCSV implements AltosWriter { + File name; + PrintStream out; + boolean header_written; + boolean seen_boost; + int boost_tick; + LinkedList<AltosRecord> pad_records; + AltosState state; + + static final int ALTOS_CSV_VERSION = 2; + + /* Version 2 format: + * + * General info + * version number + * serial number + * flight number + * callsign + * time (seconds since boost) + * rssi + * link quality + * + * Flight status + * state + * state name + * + * Basic sensors + * acceleration (m/s²) + * pressure (mBar) + * altitude (m) + * height (m) + * accelerometer speed (m/s) + * barometer speed (m/s) + * temp (°C) + * battery (V) + * drogue (V) + * main (V) + * + * GPS data + * connected (1/0) + * locked (1/0) + * nsat (used for solution) + * latitude (°) + * longitude (°) + * altitude (m) + * year (e.g. 2010) + * month (1-12) + * day (1-31) + * hour (0-23) + * minute (0-59) + * second (0-59) + * from_pad_dist (m) + * from_pad_azimuth (deg true) + * from_pad_range (m) + * from_pad_elevation (deg from horizon) + * hdop + * + * GPS Sat data + * C/N0 data for all 32 valid SDIDs + */ + + void write_general_header() { + out.printf("version,serial,flight,call,time,rssi,lqi"); + } + + void write_general(AltosRecord record) { + out.printf("%s, %d, %d, %s, %8.2f, %4d, %3d", + ALTOS_CSV_VERSION, record.serial, record.flight, record.callsign, + (double) record.time, + record.rssi, + record.status & 0x7f); + } + + void write_flight_header() { + out.printf("state,state_name"); + } + + void write_flight(AltosRecord record) { + out.printf("%d,%8s", record.state, record.state()); + } + + void write_basic_header() { + out.printf("acceleration,pressure,altitude,height,accel_speed,baro_speed,temperature,battery_voltage,drogue_voltage,main_voltage"); + } + + void write_basic(AltosRecord record) { + out.printf("%8.2f,%10.2f,%8.2f,%8.2f,%8.2f,%8.2f,%5.1f,%5.2f,%5.2f,%5.2f", + record.acceleration(), + record.raw_pressure(), + record.raw_altitude(), + record.raw_height(), + record.accel_speed(), + state.baro_speed, + record.temperature(), + record.battery_voltage(), + record.drogue_voltage(), + record.main_voltage()); + } + + void write_gps_header() { + out.printf("connected,locked,nsat,latitude,longitude,altitude,year,month,day,hour,minute,second,pad_dist,pad_range,pad_az,pad_el,hdop"); + } + + void write_gps(AltosRecord record) { + AltosGPS gps = record.gps; + if (gps == null) + gps = new AltosGPS(); + + AltosGreatCircle from_pad = state.from_pad; + if (from_pad == null) + from_pad = new AltosGreatCircle(); + + out.printf("%2d,%2d,%3d,%12.7f,%12.7f,%6d,%5d,%3d,%3d,%3d,%3d,%3d,%9.0f,%9.0f,%4.0f,%4.0f,%6.1f", + gps.connected?1:0, + gps.locked?1:0, + gps.nsat, + gps.lat, + gps.lon, + gps.alt, + gps.year, + gps.month, + gps.day, + gps.hour, + gps.minute, + gps.second, + from_pad.distance, + state.range, + from_pad.bearing, + state.elevation, + gps.hdop); + } + + void write_gps_sat_header() { + for(int i = 1; i <= 32; i++) { + out.printf("sat%02d", i); + if (i != 32) + out.printf(","); + } + } + + void write_gps_sat(AltosRecord record) { + AltosGPS gps = record.gps; + for(int i = 1; i <= 32; i++) { + int c_n0 = 0; + if (gps != null && gps.cc_gps_sat != null) { + for(int j = 0; j < gps.cc_gps_sat.length; j++) + if (gps.cc_gps_sat[j].svid == i) { + c_n0 = gps.cc_gps_sat[j].c_n0; + break; + } + } + out.printf ("%3d", c_n0); + if (i != 32) + out.printf(","); + } + } + + void write_header() { + out.printf("#"); write_general_header(); + out.printf(","); write_flight_header(); + out.printf(","); write_basic_header(); + out.printf(","); write_gps_header(); + out.printf(","); write_gps_sat_header(); + out.printf ("\n"); + } + + void write_one(AltosRecord record) { + state = new AltosState(record, state); + write_general(record); out.printf(","); + write_flight(record); out.printf(","); + write_basic(record); out.printf(","); + write_gps(record); out.printf(","); + write_gps_sat(record); + out.printf ("\n"); + } + + void flush_pad() { + while (!pad_records.isEmpty()) { + write_one (pad_records.remove()); + } + } + + public void write(AltosRecord record) { + if (!header_written) { + write_header(); + header_written = true; + } + if (!seen_boost) { + if (record.state >= Altos.ao_flight_boost) { + seen_boost = true; + boost_tick = record.tick; + flush_pad(); + } + } + if (seen_boost) + write_one(record); + else + pad_records.add(record); + } + + public PrintStream out() { + return out; + } + + public void close() { + if (!pad_records.isEmpty()) { + boost_tick = pad_records.element().tick; + flush_pad(); + } + out.close(); + } + + public void write(AltosRecordIterable iterable) { + iterable.write_comments(out()); + for (AltosRecord r : iterable) + write(r); + } + + public AltosCSV(File in_name) throws FileNotFoundException { + name = in_name; + out = new PrintStream(name); + pad_records = new LinkedList<AltosRecord>(); + } + + public AltosCSV(String in_string) throws FileNotFoundException { + this(new File(in_string)); + } +} diff --git a/altosui/AltosCSVUI.java b/altosui/AltosCSVUI.java new file mode 100644 index 00000000..e1b6002d --- /dev/null +++ b/altosui/AltosCSVUI.java @@ -0,0 +1,108 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosCSVUI + extends JDialog + implements ActionListener +{ + JFileChooser csv_chooser; + JPanel accessory; + JComboBox combo_box; + AltosRecordIterable iterable; + AltosWriter writer; + + static String[] combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" }; + + void set_default_file() { + File current = csv_chooser.getSelectedFile(); + String current_name = current.getName(); + String new_name = null; + String selected = (String) combo_box.getSelectedItem(); + + if (selected.contains("CSV")) + new_name = Altos.replace_extension(current_name, ".csv"); + else if (selected.contains("KML")) + new_name = Altos.replace_extension(current_name, ".kml"); + if (new_name != null) + csv_chooser.setSelectedFile(new File(new_name)); + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("comboBoxChanged")) + set_default_file(); + } + + public AltosCSVUI(JFrame frame, AltosRecordIterable in_iterable, File source_file) { + iterable = in_iterable; + csv_chooser = new JFileChooser(source_file); + + accessory = new JPanel(); + accessory.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.weightx = 1; + c.weighty = 0; + c.insets = new Insets (4, 4, 4, 4); + + JLabel accessory_label = new JLabel("Export File Type"); + c.gridx = 0; + c.gridy = 0; + accessory.add(accessory_label, c); + + combo_box = new JComboBox(combo_box_items); + combo_box.addActionListener(this); + c.gridx = 0; + c.gridy = 1; + accessory.add(combo_box, c); + + csv_chooser.setAccessory(accessory); + csv_chooser.setSelectedFile(source_file); + set_default_file(); + int ret = csv_chooser.showSaveDialog(frame); + if (ret == JFileChooser.APPROVE_OPTION) { + File file = csv_chooser.getSelectedFile(); + String type = (String) combo_box.getSelectedItem(); + try { + if (type.contains("CSV")) + writer = new AltosCSV(file); + else + writer = new AltosKML(file); + writer.write(iterable); + writer.close(); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(frame, + file.getName(), + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/altosui/AltosChannelMenu.java b/altosui/AltosChannelMenu.java new file mode 100644 index 00000000..abbb86f4 --- /dev/null +++ b/altosui/AltosChannelMenu.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosChannelMenu extends JComboBox implements ActionListener { + int channel; + + public AltosChannelMenu(int current_channel) { + + channel = current_channel; + + for (int c = 0; c <= 9; c++) + addItem(String.format("Channel %1d (%7.3fMHz)", c, 434.550 + c * 0.1)); + setSelectedIndex(channel); + setMaximumRowCount(10); + } + +} diff --git a/altosui/AltosConfig.java b/altosui/AltosConfig.java new file mode 100644 index 00000000..1c42870f --- /dev/null +++ b/altosui/AltosConfig.java @@ -0,0 +1,295 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +import libaltosJNI.*; + +public class AltosConfig implements Runnable, ActionListener { + + class int_ref { + int value; + + public int get() { + return value; + } + public void set(int i) { + value = i; + } + public int_ref(int i) { + value = i; + } + } + + class string_ref { + String value; + + public String get() { + return value; + } + public void set(String i) { + value = i; + } + public string_ref(String i) { + value = i; + } + } + + JFrame owner; + AltosDevice device; + AltosSerial serial_line; + boolean remote; + Thread config_thread; + int_ref serial; + int_ref main_deploy; + int_ref apogee_delay; + int_ref radio_channel; + int_ref radio_calibration; + string_ref version; + string_ref product; + string_ref callsign; + AltosConfigUI config_ui; + boolean serial_started; + + boolean get_int(String line, String label, int_ref x) { + if (line.startsWith(label)) { + try { + String tail = line.substring(label.length()).trim(); + String[] tokens = tail.split("\\s+"); + if (tokens.length > 0) { + int i = Integer.parseInt(tokens[0]); + x.set(i); + return true; + } + } catch (NumberFormatException ne) { + } + } + return false; + } + + boolean get_string(String line, String label, string_ref s) { + if (line.startsWith(label)) { + String quoted = line.substring(label.length()).trim(); + + if (quoted.startsWith("\"")) + quoted = quoted.substring(1); + if (quoted.endsWith("\"")) + quoted = quoted.substring(0,quoted.length()-1); + s.set(quoted); + return true; + } else { + return false; + } + } + + void start_serial() throws InterruptedException { + serial_started = true; + if (remote) { + serial_line.set_radio(); + serial_line.printf("p\nE 0\n"); + serial_line.flush_input(); + } + } + + void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (remote) { + serial_line.printf("~"); + serial_line.flush_output(); + } + } + + void get_data() throws InterruptedException, TimeoutException { + try { + start_serial(); + serial_line.printf("c s\nv\n"); + for (;;) { + String line = serial_line.get_reply(5000); + if (line == null) + throw new TimeoutException(); + get_int(line, "serial-number", serial); + get_int(line, "Main deploy:", main_deploy); + get_int(line, "Apogee delay:", apogee_delay); + get_int(line, "Radio channel:", radio_channel); + get_int(line, "Radio cal:", radio_calibration); + get_string(line, "Callsign:", callsign); + get_string(line,"software-version", version); + get_string(line,"product", product); + + /* signals the end of the version info */ + if (line.startsWith("software-version")) + break; + } + } finally { + stop_serial(); + } + } + + void init_ui () throws InterruptedException, TimeoutException { + config_ui = new AltosConfigUI(owner, remote); + config_ui.addActionListener(this); + set_ui(); + } + + void abort() { + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + try { + stop_serial(); + } catch (InterruptedException ie) { + } + serial_line.close(); + serial_line = null; + } + + void set_ui() throws InterruptedException, TimeoutException { + if (serial_line != null) + get_data(); + config_ui.set_serial(serial.get()); + config_ui.set_product(product.get()); + config_ui.set_version(version.get()); + config_ui.set_main_deploy(main_deploy.get()); + config_ui.set_apogee_delay(apogee_delay.get()); + config_ui.set_radio_channel(radio_channel.get()); + config_ui.set_radio_calibration(radio_calibration.get()); + config_ui.set_callsign(callsign.get()); + config_ui.set_clean(); + } + + void run_dialog() { + } + + void save_data() { + main_deploy.set(config_ui.main_deploy()); + apogee_delay.set(config_ui.apogee_delay()); + radio_channel.set(config_ui.radio_channel()); + radio_calibration.set(config_ui.radio_calibration()); + callsign.set(config_ui.callsign()); + try { + start_serial(); + serial_line.printf("c m %d\n", main_deploy.get()); + serial_line.printf("c d %d\n", apogee_delay.get()); + if (!remote) { + serial_line.printf("c r %d\n", radio_channel.get()); + serial_line.printf("c f %d\n", radio_calibration.get()); + } + serial_line.printf("c c %s\n", callsign.get()); + serial_line.printf("c w\n"); + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + try { + if (cmd.equals("Save")) { + save_data(); + set_ui(); + } else if (cmd.equals("Reset")) { + set_ui(); + } else if (cmd.equals("Reboot")) { + if (serial_line != null) { + start_serial(); + serial_line.printf("r eboot\n"); + serial_line.flush_output(); + stop_serial(); + serial_line.close(); + } + } else if (cmd.equals("Close")) { + if (serial_line != null) + serial_line.close(); + } + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public void run () { + try { + init_ui(); + config_ui.make_visible(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + public AltosConfig(JFrame given_owner) { + owner = given_owner; + + serial = new int_ref(0); + main_deploy = new int_ref(250); + apogee_delay = new int_ref(0); + radio_channel = new int_ref(0); + radio_calibration = new int_ref(1186611); + callsign = new string_ref("N0CALL"); + version = new string_ref("unknown"); + product = new string_ref("unknown"); + + device = AltosDeviceDialog.show(owner, AltosDevice.product_any); + if (device != null) { + try { + serial_line = new AltosSerial(device); + if (!device.matchProduct(AltosDevice.product_telemetrum)) + remote = true; + config_thread = new Thread(this); + config_thread.start(); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + } +}
\ No newline at end of file diff --git a/altosui/AltosConfigUI.java b/altosui/AltosConfigUI.java new file mode 100644 index 00000000..cfa5d7b9 --- /dev/null +++ b/altosui/AltosConfigUI.java @@ -0,0 +1,466 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +import libaltosJNI.*; + +public class AltosConfigUI + extends JDialog + implements ActionListener, ItemListener, DocumentListener +{ + + Container pane; + Box box; + JLabel product_label; + JLabel version_label; + JLabel serial_label; + JLabel main_deploy_label; + JLabel apogee_delay_label; + JLabel radio_channel_label; + JLabel radio_calibration_label; + JLabel callsign_label; + + public boolean dirty; + + JFrame owner; + JLabel product_value; + JLabel version_value; + JLabel serial_value; + JComboBox main_deploy_value; + JComboBox apogee_delay_value; + JComboBox radio_channel_value; + JTextField radio_calibration_value; + JTextField callsign_value; + + JButton save; + JButton reset; + JButton reboot; + JButton close; + + ActionListener listener; + + static String[] main_deploy_values = { + "100", "150", "200", "250", "300", "350", + "400", "450", "500" + }; + + static String[] apogee_delay_values = { + "0", "1", "2", "3", "4", "5" + }; + + static String[] radio_channel_values = new String[10]; + { + for (int i = 0; i <= 9; i++) + radio_channel_values[i] = String.format("Channel %1d (%7.3fMHz)", + i, 434.550 + i * 0.1); + } + + /* A window listener to catch closing events and tell the config code */ + class ConfigListener extends WindowAdapter { + AltosConfigUI ui; + + public ConfigListener(AltosConfigUI this_ui) { + ui = this_ui; + } + + public void windowClosing(WindowEvent e) { + ui.actionPerformed(new ActionEvent(e.getSource(), + ActionEvent.ACTION_PERFORMED, + "Close")); + } + } + + /* Build the UI using a grid bag */ + public AltosConfigUI(JFrame in_owner, boolean remote) { + super (in_owner, "Configure TeleMetrum", false); + + owner = in_owner; + GridBagConstraints c; + + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + /* Product */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + product_label = new JLabel("Product:"); + pane.add(product_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 0; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + product_value = new JLabel(""); + pane.add(product_value, c); + + /* Version */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 1; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + version_label = new JLabel("Software version:"); + pane.add(version_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 1; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + version_value = new JLabel(""); + pane.add(version_value, c); + + /* Serial */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 2; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + /* Main deploy */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 3; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + main_deploy_label = new JLabel("Main Deploy Altitude(m):"); + pane.add(main_deploy_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 3; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + main_deploy_value = new JComboBox(main_deploy_values); + main_deploy_value.setEditable(true); + main_deploy_value.addItemListener(this); + pane.add(main_deploy_value, c); + + /* Apogee delay */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 4; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + apogee_delay_label = new JLabel("Apogee Delay(s):"); + pane.add(apogee_delay_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 4; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + apogee_delay_value = new JComboBox(apogee_delay_values); + apogee_delay_value.setEditable(true); + apogee_delay_value.addItemListener(this); + pane.add(apogee_delay_value, c); + + /* Radio channel */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_channel_label = new JLabel("Radio Channel:"); + pane.add(radio_channel_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 5; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_channel_value = new JComboBox(radio_channel_values); + radio_channel_value.setEditable(false); + radio_channel_value.addItemListener(this); + if (remote) + radio_channel_value.setEnabled(false); + pane.add(radio_channel_value, c); + + /* Radio Calibration */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + radio_calibration_label = new JLabel("RF Calibration:"); + pane.add(radio_calibration_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 6; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + radio_calibration_value = new JTextField(String.format("%d", 1186611)); + radio_calibration_value.getDocument().addDocumentListener(this); + if (remote) + radio_calibration_value.setEnabled(false); + pane.add(radio_calibration_value, c); + + /* Callsign */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 7; + c.gridwidth = 4; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + c.ipady = 5; + callsign_label = new JLabel("Callsign:"); + pane.add(callsign_label, c); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 7; + c.gridwidth = 4; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + c.ipady = 5; + callsign_value = new JTextField(AltosPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + pane.add(callsign_value, c); + + /* Buttons */ + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 8; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + save = new JButton("Save"); + pane.add(save, c); + save.addActionListener(this); + save.setActionCommand("Save"); + + c = new GridBagConstraints(); + c.gridx = 2; c.gridy = 8; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reset = new JButton("Reset"); + pane.add(reset, c); + reset.addActionListener(this); + reset.setActionCommand("Reset"); + + c = new GridBagConstraints(); + c.gridx = 4; c.gridy = 8; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.insets = il; + reboot = new JButton("Reboot"); + pane.add(reboot, c); + reboot.addActionListener(this); + reboot.setActionCommand("Reboot"); + + c = new GridBagConstraints(); + c.gridx = 6; c.gridy = 8; + c.gridwidth = 2; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_END; + c.insets = il; + close = new JButton("Close"); + pane.add(close, c); + close.addActionListener(this); + close.setActionCommand("Close"); + + addWindowListener(new ConfigListener(this)); + } + + /* Once the initial values are set, the config code will show the dialog */ + public void make_visible() { + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + /* If any values have been changed, confirm before closing */ + public boolean check_dirty(String operation) { + if (dirty) { + Object[] options = { String.format("%s anyway", operation), "Keep editing" }; + int i; + i = JOptionPane.showOptionDialog(this, + String.format("Configuration modified. %s anyway?", operation), + "Configuration Modified", + JOptionPane.DEFAULT_OPTION, + JOptionPane.WARNING_MESSAGE, + null, options, options[1]); + if (i != 0) + return false; + } + return true; + } + + /* Listen for events from our buttons */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + if (cmd.equals("Close") || cmd.equals("Reboot")) + if (!check_dirty(cmd)) + return; + listener.actionPerformed(e); + if (cmd.equals("Close") || cmd.equals("Reboot")) { + setVisible(false); + dispose(); + } + dirty = false; + } + + /* ItemListener interface method */ + public void itemStateChanged(ItemEvent e) { + dirty = true; + } + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + dirty = true; + } + + public void insertUpdate(DocumentEvent e) { + dirty = true; + } + + public void removeUpdate(DocumentEvent e) { + dirty = true; + } + + /* Let the config code hook on a listener */ + public void addActionListener(ActionListener l) { + listener = l; + } + + /* set and get all of the dialog values */ + public void set_product(String product) { + product_value.setText(product); + } + + public void set_version(String version) { + version_value.setText(version); + } + + public void set_serial(int serial) { + serial_value.setText(String.format("%d", serial)); + } + + public void set_main_deploy(int new_main_deploy) { + main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy)); + } + + public int main_deploy() { + return Integer.parseInt(main_deploy_value.getSelectedItem().toString()); + } + + public void set_apogee_delay(int new_apogee_delay) { + apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay)); + } + + public int apogee_delay() { + return Integer.parseInt(apogee_delay_value.getSelectedItem().toString()); + } + + public void set_radio_channel(int new_radio_channel) { + radio_channel_value.setSelectedIndex(new_radio_channel); + } + + public int radio_channel() { + return radio_channel_value.getSelectedIndex(); + } + + public void set_radio_calibration(int new_radio_calibration) { + radio_calibration_value.setText(String.format("%d", new_radio_calibration)); + } + + public int radio_calibration() { + return Integer.parseInt(radio_calibration_value.getText()); + } + + public void set_callsign(String new_callsign) { + callsign_value.setText(new_callsign); + } + + public String callsign() { + return callsign_value.getText(); + } + + public void set_clean() { + dirty = false; + } + + }
\ No newline at end of file diff --git a/altosui/AltosConfigureUI.java b/altosui/AltosConfigureUI.java new file mode 100644 index 00000000..153c59fd --- /dev/null +++ b/altosui/AltosConfigureUI.java @@ -0,0 +1,187 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import javax.swing.event.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosConfigureUI + extends JDialog + implements DocumentListener +{ + JFrame owner; + AltosVoice voice; + Container pane; + + JRadioButton enable_voice; + JButton test_voice; + JButton close; + + JButton configure_log; + JTextField log_directory; + + JLabel callsign_label; + JTextField callsign_value; + + /* DocumentListener interface methods */ + public void changedUpdate(DocumentEvent e) { + AltosPreferences.set_callsign(callsign_value.getText()); + } + + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + + public AltosConfigureUI(JFrame in_owner, AltosVoice in_voice) { + super(in_owner, "Configure AltosUI", false); + + GridBagConstraints c; + + Insets insets = new Insets(4, 4, 4, 4); + + owner = in_owner; + voice = in_voice; + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.insets = insets; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + + /* Nice label at the top */ + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + pane.add(new JLabel ("Configure AltOS UI"), c); + + /* Voice settings */ + c.gridx = 0; + c.gridy = 1; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Voice"), c); + + enable_voice = new JRadioButton("Enable", AltosPreferences.voice()); + enable_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JRadioButton item = (JRadioButton) e.getSource(); + boolean enabled = item.isSelected(); + AltosPreferences.set_voice(enabled); + if (enabled) + voice.speak_always("Enable voice."); + else + voice.speak_always("Disable voice."); + } + }); + c.gridx = 1; + c.gridy = 1; + c.gridwidth = 1; + c.weightx = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(enable_voice, c); + + c.gridx = 2; + c.gridy = 1; + c.gridwidth = 1; + c.weightx = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + test_voice = new JButton("Test Voice"); + test_voice.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + voice.speak("That's one small step for man; one giant leap for mankind."); + } + }); + pane.add(test_voice, c); + + /* Log directory settings */ + c.gridx = 0; + c.gridy = 2; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Log Directory"), c); + + configure_log = new JButton(AltosPreferences.logdir().getPath()); + configure_log.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + AltosPreferences.ConfigureLog(); + configure_log.setText(AltosPreferences.logdir().getPath()); + } + }); + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(configure_log, c); + + /* Callsign setting */ + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.WEST; + pane.add(new JLabel("Callsign"), c); + + callsign_value = new JTextField(AltosPreferences.callsign()); + callsign_value.getDocument().addDocumentListener(this); + c.gridx = 1; + c.gridy = 3; + c.gridwidth = 2; + c.fill = GridBagConstraints.BOTH; + c.anchor = GridBagConstraints.WEST; + pane.add(callsign_value, c); + + /* And a close button at the bottom */ + close = new JButton("Close"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 3; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + pane.add(close, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } +} diff --git a/altosui/AltosConvert.java b/altosui/AltosConvert.java new file mode 100644 index 00000000..8cc1df27 --- /dev/null +++ b/altosui/AltosConvert.java @@ -0,0 +1,192 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +/* + * Sensor data conversion functions + */ +package altosui; + +public class AltosConvert { + /* + * Pressure Sensor Model, version 1.1 + * + * written by Holly Grimes + * + * Uses the International Standard Atmosphere as described in + * "A Quick Derivation relating altitude to air pressure" (version 1.03) + * from the Portland State Aerospace Society, except that the atmosphere + * is divided into layers with each layer having a different lapse rate. + * + * Lapse rate data for each layer was obtained from Wikipedia on Sept. 1, 2007 + * at site <http://en.wikipedia.org/wiki/International_Standard_Atmosphere + * + * Height measurements use the local tangent plane. The postive z-direction is up. + * + * All measurements are given in SI units (Kelvin, Pascal, meter, meters/second^2). + * The lapse rate is given in Kelvin/meter, the gas constant for air is given + * in Joules/(kilogram-Kelvin). + */ + + static final double GRAVITATIONAL_ACCELERATION = -9.80665; + static final double AIR_GAS_CONSTANT = 287.053; + static final double NUMBER_OF_LAYERS = 7; + static final double MAXIMUM_ALTITUDE = 84852.0; + static final double MINIMUM_PRESSURE = 0.3734; + static final double LAYER0_BASE_TEMPERATURE = 288.15; + static final double LAYER0_BASE_PRESSURE = 101325; + + /* lapse rate and base altitude for each layer in the atmosphere */ + static final double[] lapse_rate = { + -0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002 + }; + + static final int[] base_altitude = { + 0, 11000, 20000, 32000, 47000, 51000, 71000 + }; + + /* outputs atmospheric pressure associated with the given altitude. + * altitudes are measured with respect to the mean sea level + */ + static double + altitude_to_pressure(double altitude) + { + double base_temperature = LAYER0_BASE_TEMPERATURE; + double base_pressure = LAYER0_BASE_PRESSURE; + + double pressure; + double base; /* base for function to determine pressure */ + double exponent; /* exponent for function to determine pressure */ + int layer_number; /* identifies layer in the atmosphere */ + double delta_z; /* difference between two altitudes */ + + if (altitude > MAXIMUM_ALTITUDE) /* FIX ME: use sensor data to improve model */ + return 0; + + /* calculate the base temperature and pressure for the atmospheric layer + associated with the inputted altitude */ + for(layer_number = 0; layer_number < NUMBER_OF_LAYERS - 1 && altitude > base_altitude[layer_number + 1]; layer_number++) { + delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number]; + if (lapse_rate[layer_number] == 0.0) { + exponent = GRAVITATIONAL_ACCELERATION * delta_z + / AIR_GAS_CONSTANT / base_temperature; + base_pressure *= Math.exp(exponent); + } + else { + base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; + exponent = GRAVITATIONAL_ACCELERATION / + (AIR_GAS_CONSTANT * lapse_rate[layer_number]); + base_pressure *= Math.pow(base, exponent); + } + base_temperature += delta_z * lapse_rate[layer_number]; + } + + /* calculate the pressure at the inputted altitude */ + delta_z = altitude - base_altitude[layer_number]; + if (lapse_rate[layer_number] == 0.0) { + exponent = GRAVITATIONAL_ACCELERATION * delta_z + / AIR_GAS_CONSTANT / base_temperature; + pressure = base_pressure * Math.exp(exponent); + } + else { + base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; + exponent = GRAVITATIONAL_ACCELERATION / + (AIR_GAS_CONSTANT * lapse_rate[layer_number]); + pressure = base_pressure * Math.pow(base, exponent); + } + + return pressure; + } + + +/* outputs the altitude associated with the given pressure. the altitude + returned is measured with respect to the mean sea level */ + static double + pressure_to_altitude(double pressure) + { + + double next_base_temperature = LAYER0_BASE_TEMPERATURE; + double next_base_pressure = LAYER0_BASE_PRESSURE; + + double altitude; + double base_pressure; + double base_temperature; + double base; /* base for function to determine base pressure of next layer */ + double exponent; /* exponent for function to determine base pressure + of next layer */ + double coefficient; + int layer_number; /* identifies layer in the atmosphere */ + int delta_z; /* difference between two altitudes */ + + if (pressure < 0) /* illegal pressure */ + return -1; + if (pressure < MINIMUM_PRESSURE) /* FIX ME: use sensor data to improve model */ + return MAXIMUM_ALTITUDE; + + /* calculate the base temperature and pressure for the atmospheric layer + associated with the inputted pressure. */ + layer_number = -1; + do { + layer_number++; + base_pressure = next_base_pressure; + base_temperature = next_base_temperature; + delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number]; + if (lapse_rate[layer_number] == 0.0) { + exponent = GRAVITATIONAL_ACCELERATION * delta_z + / AIR_GAS_CONSTANT / base_temperature; + next_base_pressure *= Math.exp(exponent); + } + else { + base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0; + exponent = GRAVITATIONAL_ACCELERATION / + (AIR_GAS_CONSTANT * lapse_rate[layer_number]); + next_base_pressure *= Math.pow(base, exponent); + } + next_base_temperature += delta_z * lapse_rate[layer_number]; + } + while(layer_number < NUMBER_OF_LAYERS - 1 && pressure < next_base_pressure); + + /* calculate the altitude associated with the inputted pressure */ + if (lapse_rate[layer_number] == 0.0) { + coefficient = (AIR_GAS_CONSTANT / GRAVITATIONAL_ACCELERATION) + * base_temperature; + altitude = base_altitude[layer_number] + + coefficient * Math.log(pressure / base_pressure); + } + else { + base = pressure / base_pressure; + exponent = AIR_GAS_CONSTANT * lapse_rate[layer_number] + / GRAVITATIONAL_ACCELERATION; + coefficient = base_temperature / lapse_rate[layer_number]; + altitude = base_altitude[layer_number] + + coefficient * (Math.pow(base, exponent) - 1); + } + + return altitude; + } + + static double + cc_battery_to_voltage(double battery) + { + return battery / 32767.0 * 5.0; + } + + static double + cc_ignitor_to_voltage(double ignite) + { + return ignite / 32767 * 15.0; + } +} diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java new file mode 100644 index 00000000..15de05c2 --- /dev/null +++ b/altosui/AltosDataChooser.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; + +public class AltosDataChooser extends JFileChooser { + JFrame frame; + String filename; + File file; + + public String filename() { + return filename; + } + + public File file() { + return file; + } + + public AltosRecordIterable runDialog() { + int ret; + + ret = showOpenDialog(frame); + if (ret == APPROVE_OPTION) { + file = getSelectedFile(); + if (file == null) + return null; + filename = file.getName(); + try { + if (filename.endsWith("eeprom")) { + FileInputStream in = new FileInputStream(file); + return new AltosEepromIterable(in); + } else if (filename.endsWith("telem")) { + FileInputStream in = new FileInputStream(file); + return new AltosTelemetryIterable(in); + } else { + throw new FileNotFoundException(); + } + } catch (FileNotFoundException fe) { + JOptionPane.showMessageDialog(frame, + filename, + "Cannot open file", + JOptionPane.ERROR_MESSAGE); + } + } + return null; + } + + public AltosDataChooser(JFrame in_frame) { + frame = in_frame; + setDialogTitle("Select Flight Record File"); + setFileFilter(new FileNameExtensionFilter("Flight data file", + "telem", "eeprom")); + setCurrentDirectory(AltosPreferences.logdir()); + } +} diff --git a/altosui/AltosDataPoint.java b/altosui/AltosDataPoint.java new file mode 100644 index 00000000..66313e03 --- /dev/null +++ b/altosui/AltosDataPoint.java @@ -0,0 +1,29 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +interface AltosDataPoint { + int version(); + int serial(); + int flight(); + String callsign(); + double time(); + double rssi(); + + int state(); + String state_name(); + + double acceleration(); + double pressure(); + double altitude(); + double height(); + double accel_speed(); + double baro_speed(); + double temperature(); + double battery_voltage(); + double drogue_voltage(); + double main_voltage(); +} + diff --git a/altosui/AltosDataPointReader.java b/altosui/AltosDataPointReader.java new file mode 100644 index 00000000..7704310b --- /dev/null +++ b/altosui/AltosDataPointReader.java @@ -0,0 +1,72 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.IOException; +import java.text.ParseException; +import java.lang.UnsupportedOperationException; +import java.util.NoSuchElementException; +import java.util.Iterator; + +class AltosDataPointReader implements Iterable<AltosDataPoint> { + Iterator<AltosRecord> iter; + AltosState state; + AltosRecord record; + + public AltosDataPointReader(Iterable<AltosRecord> reader) { + this.iter = reader.iterator(); + this.state = null; + } + + private void read_next_record() + throws NoSuchElementException + { + record = iter.next(); + state = new AltosState(record, state); + } + + private AltosDataPoint current_dp() { + assert this.record != null; + + return new AltosDataPoint() { + public int version() { return record.version; } + public int serial() { return record.serial; } + public int flight() { return record.flight; } + public String callsign() { return record.callsign; } + public double time() { return record.time; } + public double rssi() { return record.rssi; } + + public int state() { return record.state; } + public String state_name() { return record.state(); } + + public double acceleration() { return record.acceleration(); } + public double pressure() { return record.raw_pressure(); } + public double altitude() { return record.raw_altitude(); } + public double height() { return record.raw_height(); } + public double accel_speed() { return record.accel_speed(); } + public double baro_speed() { return state.baro_speed; } + public double temperature() { return record.temperature(); } + public double battery_voltage() { return record.battery_voltage(); } + public double drogue_voltage() { return record.drogue_voltage(); } + public double main_voltage() { return record.main_voltage(); } + }; + } + + public Iterator<AltosDataPoint> iterator() { + return new Iterator<AltosDataPoint>() { + public void remove() { + throw new UnsupportedOperationException(); + } + public boolean hasNext() { + return iter.hasNext(); + } + public AltosDataPoint next() { + read_next_record(); + return current_dp(); + } + }; + } +} + diff --git a/altosui/AltosDebug.java b/altosui/AltosDebug.java new file mode 100644 index 00000000..8d435b66 --- /dev/null +++ b/altosui/AltosDebug.java @@ -0,0 +1,267 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; + +import libaltosJNI.*; + +public class AltosDebug extends AltosSerial { + + public static final byte WR_CONFIG = 0x1d; + public static final byte RD_CONFIG = 0x24; + public static final byte CONFIG_TIMERS_OFF = (1 << 3); + public static final byte CONFIG_DMA_PAUSE = (1 << 2); + public static final byte CONFIG_TIMER_SUSPEND = (1 << 1); + public static final byte SET_FLASH_INFO_PAGE = (1 << 0); + + public static final byte GET_PC = 0x28; + public static final byte READ_STATUS = 0x34; + public static final byte STATUS_CHIP_ERASE_DONE = (byte) (1 << 7); + public static final byte STATUS_PCON_IDLE = (1 << 6); + public static final byte STATUS_CPU_HALTED = (1 << 5); + public static final byte STATUS_POWER_MODE_0 = (1 << 4); + public static final byte STATUS_HALT_STATUS = (1 << 3); + public static final byte STATUS_DEBUG_LOCKED = (1 << 2); + public static final byte STATUS_OSCILLATOR_STABLE = (1 << 1); + public static final byte STATUS_STACK_OVERFLOW = (1 << 0); + + public static final byte SET_HW_BRKPNT = 0x3b; + public static byte HW_BRKPNT_N(byte n) { return (byte) ((n) << 3); } + public static final byte HW_BRKPNT_N_MASK = (0x3 << 3); + public static final byte HW_BRKPNT_ENABLE = (1 << 2); + + public static final byte HALT = 0x44; + public static final byte RESUME = 0x4c; + public static byte DEBUG_INSTR(byte n) { return (byte) (0x54|(n)); } + public static final byte STEP_INSTR = 0x5c; + public static byte STEP_REPLACE(byte n) { return (byte) (0x64|(n)); } + public static final byte GET_CHIP_ID = 0x68; + + + boolean debug_mode; + + void ensure_debug_mode() { + if (!debug_mode) { + printf("D\n"); + flush_input(); + debug_mode = true; + } + } + + void dump_memory(String header, int address, byte[] bytes, int start, int len) { + System.out.printf("%s\n", header); + for (int j = 0; j < len; j++) { + if ((j & 15) == 0) { + if (j != 0) + System.out.printf("\n"); + System.out.printf ("%04x:", address + j); + } + System.out.printf(" %02x", bytes[start + j]); + } + System.out.printf("\n"); + } + + /* + * Write target memory + */ + public void write_memory(int address, byte[] bytes, int start, int len) { + ensure_debug_mode(); +// dump_memory("write_memory", address, bytes, start, len); + printf("O %x %x\n", len, address); + for (int i = 0; i < len; i++) + printf("%02x", bytes[start + i]); + } + + public void write_memory(int address, byte[] bytes) { + write_memory(address, bytes, 0, bytes.length); + } + + /* + * Read target memory + */ + public byte[] read_memory(int address, int length) + throws IOException, InterruptedException { + byte[] data = new byte[length]; + + flush_input(); + ensure_debug_mode(); + printf("I %x %x\n", length, address); + int i = 0; + int start = 0; + while (i < length) { + String line = get_reply().trim(); + if (!Altos.ishex(line) || line.length() % 2 != 0) + throw new IOException( + String.format + ("Invalid reply \"%s\"", line)); + int this_time = line.length() / 2; + for (int j = 0; j < this_time; j++) + data[start + j] = (byte) ((Altos.fromhex(line.charAt(j*2)) << 4) + + Altos.fromhex(line.charAt(j*2+1))); + start += this_time; + i += this_time; + } +// dump_memory("read_memory", address, data, 0, length); + + return data; + } + + /* + * Write raw bytes to the debug link using the 'P' command + */ + public void write_bytes(byte[] bytes) throws IOException { + int i = 0; + ensure_debug_mode(); + while (i < bytes.length) { + int this_time = bytes.length - i; + if (this_time > 8) + this_time = 0; + printf("P"); + for (int j = 0; j < this_time; j++) + printf(" %02x", bytes[i+j]); + printf("\n"); + i += this_time; + } + } + + public void write_byte(byte b) throws IOException { + byte[] bytes = { b }; + write_bytes(bytes); + } + + /* + * Read raw bytes from the debug link using the 'G' command + */ + public byte[] read_bytes(int length) + throws IOException, InterruptedException { + + flush_input(); + ensure_debug_mode(); + printf("G %x\n", length); + int i = 0; + byte[] data = new byte[length]; + while (i < length) { + String line = get_reply().trim(); + String tokens[] = line.split("\\s+"); + for (int j = 0; j < tokens.length; j++) { + if (!Altos.ishex(tokens[j]) || + tokens[j].length() != 2) + throw new IOException( + String.format + ("Invalid read_bytes reply \"%s\"", line)); + try { + data[i + j] = (byte) Integer.parseInt(tokens[j], 16); + } catch (NumberFormatException ne) { + throw new IOException( + String.format + ("Invalid read_bytes reply \"%s\"", line)); + } + } + i += tokens.length; + } + return data; + } + + public byte read_byte() throws IOException, InterruptedException { + return read_bytes(1)[0]; + } + + public byte debug_instr(byte[] instruction) throws IOException, InterruptedException { + byte[] command = new byte[1 + instruction.length]; + command[0] = DEBUG_INSTR((byte) instruction.length); + for (int i = 0; i < instruction.length; i++) + command[i+1] = instruction[i]; + write_bytes(command); + return read_byte(); + } + + public byte resume() throws IOException, InterruptedException { + write_byte(RESUME); + return read_byte(); + } + + public int read_uint16() throws IOException, InterruptedException { + byte[] d = read_bytes(2); + return ((int) (d[0] & 0xff) << 8) | (d[1] & 0xff); + } + + public int read_uint8() throws IOException, InterruptedException { + byte[] d = read_bytes(1); + return (int) (d[0] & 0xff); + } + + public int get_chip_id() throws IOException, InterruptedException { + write_byte(GET_CHIP_ID); + return read_uint16(); + } + + public int get_pc() throws IOException, InterruptedException { + write_byte(GET_PC); + return read_uint16(); + } + + public byte read_status() throws IOException, InterruptedException { + write_byte(READ_STATUS); + return read_byte(); + } + + static final byte LJMP = 0x02; + + public void set_pc(int pc) throws IOException, InterruptedException { + byte high = (byte) (pc >> 8); + byte low = (byte) pc; + byte[] jump_mem = { LJMP, high, low }; + debug_instr(jump_mem); + } + + public boolean check_connection() throws IOException, InterruptedException { + byte reply = read_status(); + if ((reply & STATUS_CHIP_ERASE_DONE) == 0) + return false; + if ((reply & STATUS_PCON_IDLE) != 0) + return false; + if ((reply & STATUS_POWER_MODE_0) == 0) + return false; + return true; + } + + public AltosRomconfig romconfig() { + try { + byte[] bytes = read_memory(0xa0, 10); + return new AltosRomconfig(bytes, 0); + } catch (IOException ie) { + } catch (InterruptedException ie) { + } + return new AltosRomconfig(); + } + + /* + * Reset target + */ + public void reset() { + printf ("R\n"); + } + + public AltosDebug (AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + super(in_device); + } +}
\ No newline at end of file diff --git a/altosui/AltosDescent.java b/altosui/AltosDescent.java new file mode 100644 index 00000000..16ccd458 --- /dev/null +++ b/altosui/AltosDescent.java @@ -0,0 +1,353 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosDescent extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public abstract class DescentStatus { + JLabel label; + JTextField value; + AltosLights lights; + + abstract void show(AltosState state, int crc_errors); + void reset() { + value.setText(""); + lights.set(false); + } + + public DescentStatus (GridBagLayout layout, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + lights = new AltosLights(); + c.gridx = 0; c.gridy = y; + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(lights, c); + add(lights); + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.gridwidth = 3; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = 4; c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value, c); + add(value); + + } + } + + public abstract class DescentValue { + JLabel label; + JTextField value; + + void reset() { + value.setText(""); + } + + abstract void show(AltosState state, int crc_errors); + + void show(String format, double v) { + value.setText(String.format(format, v)); + } + + void show(String v) { + value.setText(v); + } + + public DescentValue (GridBagLayout layout, int x, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = x + 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + add(label, c); + + value = new JTextField(Altos.text_width); + value.setFont(Altos.value_font); + value.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 2; c.gridy = y; + c.gridwidth = 1; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + add(value, c); + } + } + + public abstract class DescentDualValue { + JLabel label; + JTextField value1; + JTextField value2; + + void reset() { + value1.setText(""); + value2.setText(""); + } + + abstract void show(AltosState state, int crc_errors); + void show(String v1, String v2) { + value1.setText(v1); + value2.setText(v2); + } + void show(String f1, double v1, String f2, double v2) { + value1.setText(String.format(f1, v1)); + value2.setText(String.format(f2, v2)); + } + + public DescentDualValue (GridBagLayout layout, int x, int y, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.label_font); + label.setHorizontalAlignment(SwingConstants.LEFT); + c.gridx = x + 1; c.gridy = y; + c.insets = new Insets(Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad, Altos.tab_elt_pad); + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.VERTICAL; + c.weightx = 0; + layout.setConstraints(label, c); + add(label); + + value1 = new JTextField(Altos.text_width); + value1.setFont(Altos.value_font); + value1.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 2; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + layout.setConstraints(value1, c); + add(value1); + + value2 = new JTextField(Altos.text_width); + value2.setFont(Altos.value_font); + value2.setHorizontalAlignment(SwingConstants.RIGHT); + c.gridx = x + 4; c.gridy = y; + c.anchor = GridBagConstraints.WEST; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.gridwidth = 1; + layout.setConstraints(value2, c); + add(value2); + } + } + + class Height extends DescentValue { + void show (AltosState state, int crc_errors) { + show("%6.0f m", state.height); + } + public Height (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Height"); + } + } + + Height height; + + class Speed extends DescentValue { + void show (AltosState state, int crc_errors) { + double speed = state.speed; + if (!state.ascent) + speed = state.baro_speed; + show("%6.0f m/s", speed); + } + public Speed (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Speed"); + } + } + + Speed speed; + + String pos(double p, String pos, String neg) { + String h = pos; + if (p < 0) { + h = neg; + p = -p; + } + int deg = (int) Math.floor(p); + double min = (p - Math.floor(p)) * 60.0; + return String.format("%s %d° %9.6f", h, deg, min); + } + + class Lat extends DescentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null) + show(pos(state.gps.lat,"N", "S")); + else + show("???"); + } + public Lat (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Latitude"); + } + } + + Lat lat; + + class Lon extends DescentValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null) + show(pos(state.gps.lon,"W", "E")); + else + show("???"); + } + public Lon (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Longitude"); + } + } + + Lon lon; + + class Apogee extends DescentStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.2f V", state.drogue_sense)); + lights.set(state.drogue_sense > 3.2); + } + public Apogee (GridBagLayout layout, int y) { + super(layout, y, "Apogee Igniter Voltage"); + } + } + + Apogee apogee; + + class Main extends DescentStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4.2f V", state.main_sense)); + lights.set(state.main_sense > 3.2); + } + public Main (GridBagLayout layout, int y) { + super(layout, y, "Main Igniter Voltage"); + } + } + + Main main; + + class Bearing extends DescentDualValue { + void show (AltosState state, int crc_errors) { + if (state.from_pad != null) { + show( String.format("%3.0f°", state.from_pad.bearing), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_LONG)); + } else { + show("???", "???"); + } + } + public Bearing (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Bearing"); + } + } + + Bearing bearing; + + class Range extends DescentValue { + void show (AltosState state, int crc_errors) { + show("%6.0f m", state.range); + } + public Range (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Range"); + } + } + + Range range; + + class Elevation extends DescentValue { + void show (AltosState state, int crc_errors) { + show("%3.0f°", state.elevation); + } + public Elevation (GridBagLayout layout, int x, int y) { + super (layout, x, y, "Elevation"); + } + } + + Elevation elevation; + + public void reset() { + lat.reset(); + lon.reset(); + height.reset(); + speed.reset(); + bearing.reset(); + range.reset(); + elevation.reset(); + main.reset(); + apogee.reset(); + } + + public void show(AltosState state, int crc_errors) { + height.show(state, crc_errors); + speed.show(state, crc_errors); + bearing.show(state, crc_errors); + range.show(state, crc_errors); + elevation.show(state, crc_errors); + lat.show(state, crc_errors); + lon.show(state, crc_errors); + main.show(state, crc_errors); + apogee.show(state, crc_errors); + } + + public AltosDescent() { + layout = new GridBagLayout(); + + setLayout(layout); + + /* Elements in descent display */ + speed = new Speed(layout, 0, 0); + height = new Height(layout, 2, 0); + elevation = new Elevation(layout, 0, 1); + range = new Range(layout, 2, 1); + bearing = new Bearing(layout, 0, 2); + lat = new Lat(layout, 0, 3); + lon = new Lon(layout, 2, 3); + + apogee = new Apogee(layout, 4); + main = new Main(layout, 5); + } +} diff --git a/altosui/AltosDevice.java b/altosui/AltosDevice.java new file mode 100644 index 00000000..f0fda57b --- /dev/null +++ b/altosui/AltosDevice.java @@ -0,0 +1,170 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; +import java.lang.*; +import java.util.*; +import libaltosJNI.*; + +public class AltosDevice extends altos_device { + + static public boolean initialized = false; + static public boolean loaded_library = false; + + public static boolean load_library() { + if (!initialized) { + try { + System.loadLibrary("altos"); + libaltos.altos_init(); + loaded_library = true; + } catch (UnsatisfiedLinkError e) { + loaded_library = false; + } + initialized = true; + } + return loaded_library; + } + + static int usb_vendor_altusmetrum() { + if (load_library()) + return libaltosConstants.USB_VENDOR_ALTUSMETRUM; + return 0x000a; + } + + static int usb_product_altusmetrum() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_ALTUSMETRUM; + return 0x000a; + } + + static int usb_product_altusmetrum_min() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MIN; + return 0x000a; + } + + static int usb_product_altusmetrum_max() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_ALTUSMETRUM_MAX; + return 0x000d; + } + + static int usb_product_telemetrum() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_TELEMETRUM; + return 0x000b; + } + + static int usb_product_teledongle() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_TELEDONGLE; + return 0x000c; + } + + static int usb_product_teleterra() { + if (load_library()) + return libaltosConstants.USB_PRODUCT_TELETERRA; + return 0x000d; + } + + public final static int vendor_altusmetrum = usb_vendor_altusmetrum(); + public final static int product_altusmetrum = usb_product_altusmetrum(); + public final static int product_telemetrum = usb_product_telemetrum(); + public final static int product_teledongle = usb_product_teledongle(); + public final static int product_teleterra = usb_product_teleterra(); + public final static int product_altusmetrum_min = usb_product_altusmetrum_min(); + public final static int product_altusmetrum_max = usb_product_altusmetrum_max(); + + + public final static int product_any = 0x10000; + public final static int product_basestation = 0x10000 + 1; + + public String toString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%-20.20s %4d %s", + getName(), getSerial(), getPath()); + } + + public String toShortString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%s %d %s", + name, getSerial(), getPath()); + + } + + public boolean isAltusMetrum() { + if (getVendor() != vendor_altusmetrum) + return false; + if (getProduct() < product_altusmetrum_min) + return false; + if (getProduct() > product_altusmetrum_max) + return false; + return true; + } + + public boolean matchProduct(int want_product) { + + if (!isAltusMetrum()) + return false; + + if (want_product == product_any) + return true; + + if (want_product == product_basestation) + return matchProduct(product_teledongle) || matchProduct(product_teleterra); + + int have_product = getProduct(); + + if (have_product == product_altusmetrum) /* old devices match any request */ + return true; + + if (want_product == have_product) + return true; + + return false; + } + + static AltosDevice[] list(int product) { + if (!load_library()) + return null; + + SWIGTYPE_p_altos_list list = libaltos.altos_list_start(); + + ArrayList<AltosDevice> device_list = new ArrayList<AltosDevice>(); + if (list != null) { + SWIGTYPE_p_altos_file file; + + for (;;) { + AltosDevice device = new AltosDevice(); + if (libaltos.altos_list_next(list, device) == 0) + break; + if (device.matchProduct(product)) + device_list.add(device); + } + libaltos.altos_list_finish(list); + } + + AltosDevice[] devices = new AltosDevice[device_list.size()]; + for (int i = 0; i < device_list.size(); i++) + devices[i] = device_list.get(i); + return devices; + } +}
\ No newline at end of file diff --git a/altosui/AltosDeviceDialog.java b/altosui/AltosDeviceDialog.java new file mode 100644 index 00000000..2966ad1e --- /dev/null +++ b/altosui/AltosDeviceDialog.java @@ -0,0 +1,164 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.util.*; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import libaltosJNI.libaltos; +import libaltosJNI.altos_device; +import libaltosJNI.SWIGTYPE_p_altos_file; +import libaltosJNI.SWIGTYPE_p_altos_list; + +public class AltosDeviceDialog extends JDialog implements ActionListener { + + private static AltosDeviceDialog dialog; + private static AltosDevice value = null; + private JList list; + + public static AltosDevice show (Component frameComp, int product) { + + Frame frame = JOptionPane.getFrameForComponent(frameComp); + AltosDevice[] devices; + devices = AltosDevice.list(product); + + if (devices != null && devices.length > 0) { + value = null; + dialog = new AltosDeviceDialog(frame, frameComp, + devices, + devices[0]); + + dialog.setVisible(true); + return value; + } else { + /* check for missing altos JNI library, which + * will put up its own error dialog + */ + if (AltosUI.load_library(frame)) { + JOptionPane.showMessageDialog(frame, + "No AltOS devices available", + "No AltOS devices", + JOptionPane.ERROR_MESSAGE); + } + return null; + } + } + + private AltosDeviceDialog (Frame frame, Component location, + AltosDevice[] devices, + AltosDevice initial) { + super(frame, "Device Selection", true); + + value = null; + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(this); + + final JButton selectButton = new JButton("Select"); + selectButton.setActionCommand("select"); + selectButton.addActionListener(this); + getRootPane().setDefaultButton(selectButton); + + list = new JList(devices) { + //Subclass JList to workaround bug 4832765, which can cause the + //scroll pane to not let the user easily scroll up to the beginning + //of the list. An alternative would be to set the unitIncrement + //of the JScrollBar to a fixed value. You wouldn't get the nice + //aligned scrolling, but it should work. + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, + int direction) { + int row; + if (orientation == SwingConstants.VERTICAL && + direction < 0 && (row = getFirstVisibleIndex()) != -1) { + Rectangle r = getCellBounds(row, row); + if ((r.y == visibleRect.y) && (row != 0)) { + Point loc = r.getLocation(); + loc.y--; + int prevIndex = locationToIndex(loc); + Rectangle prevR = getCellBounds(prevIndex, prevIndex); + + if (prevR == null || prevR.y >= r.y) { + return 0; + } + return prevR.height; + } + } + return super.getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + }; + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + selectButton.doClick(); //emulate button click + } + } + }); + JScrollPane listScroller = new JScrollPane(list); + listScroller.setPreferredSize(new Dimension(400, 80)); + listScroller.setAlignmentX(LEFT_ALIGNMENT); + + //Create a container so that we can add a title around + //the scroll pane. Can't add a title directly to the + //scroll pane because its background would be white. + //Lay out the label and scroll pane from top to bottom. + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); + + JLabel label = new JLabel("Select Device"); + label.setLabelFor(list); + listPane.add(label); + listPane.add(Box.createRigidArea(new Dimension(0,5))); + listPane.add(listScroller); + listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + //Lay out the buttons from left to right. + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(Box.createHorizontalGlue()); + buttonPane.add(cancelButton); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(selectButton); + + //Put everything together, using the content pane's BorderLayout. + Container contentPane = getContentPane(); + contentPane.add(listPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.PAGE_END); + + //Initialize values. + list.setSelectedValue(initial, true); + pack(); + setLocationRelativeTo(location); + } + + //Handle clicks on the Set and Cancel buttons. + public void actionPerformed(ActionEvent e) { + if ("select".equals(e.getActionCommand())) + AltosDeviceDialog.value = (AltosDevice)(list.getSelectedValue()); + AltosDeviceDialog.dialog.setVisible(false); + } + +} diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java new file mode 100644 index 00000000..3e719130 --- /dev/null +++ b/altosui/AltosDisplayThread.java @@ -0,0 +1,249 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosDisplayThread extends Thread { + + Frame parent; + IdleThread idle_thread; + AltosVoice voice; + String name; + AltosFlightReader reader; + int crc_errors; + AltosFlightDisplay display; + + synchronized void show(AltosState state, int crc_errors) { + if (state != null) + display.show(state, crc_errors); + } + + class IdleThread extends Thread { + + boolean started; + private AltosState state; + int reported_landing; + int report_interval; + long report_time; + + public synchronized void report(boolean last) { + if (state == null) + return; + + /* reset the landing count once we hear about a new flight */ + if (state.state < Altos.ao_flight_drogue) + reported_landing = 0; + + /* Shut up once the rocket is on the ground */ + if (reported_landing > 2) { + return; + } + + /* If the rocket isn't on the pad, then report height */ + if (Altos.ao_flight_drogue <= state.state && + state.state < Altos.ao_flight_landed && + state.range >= 0) + { + voice.speak("Height %d, bearing %s %d, elevation %d, range %d.\n", + (int) (state.height + 0.5), + state.from_pad.bearing_words( + AltosGreatCircle.BEARING_VOICE), + (int) (state.from_pad.bearing + 0.5), + (int) (state.elevation + 0.5), + (int) (state.range + 0.5)); + } else if (state.state > Altos.ao_flight_pad) { + voice.speak("%d meters", (int) (state.height + 0.5)); + } else { + reported_landing = 0; + } + + /* If the rocket is coming down, check to see if it has landed; + * either we've got a landed report or we haven't heard from it in + * a long time + */ + if (state.state >= Altos.ao_flight_drogue && + (last || + System.currentTimeMillis() - state.report_time >= 15000 || + state.state == Altos.ao_flight_landed)) + { + if (Math.abs(state.baro_speed) < 20 && state.height < 100) + voice.speak("rocket landed safely"); + else + voice.speak("rocket may have crashed"); + if (state.from_pad != null) + voice.speak("Bearing %d degrees, range %d meters.", + (int) (state.from_pad.bearing + 0.5), + (int) (state.from_pad.distance + 0.5)); + ++reported_landing; + if (state.state != Altos.ao_flight_landed) { + state.state = Altos.ao_flight_landed; + show(state, 0); + } + } + } + + long now () { + return System.currentTimeMillis(); + } + + void set_report_time() { + report_time = now() + report_interval; + } + + public void run () { + try { + for (;;) { + set_report_time(); + for (;;) { + voice.drain(); + synchronized (this) { + long sleep_time = report_time - now(); + if (sleep_time <= 0) + break; + wait(sleep_time); + } + } + report(false); + } + } catch (InterruptedException ie) { + try { + voice.drain(); + } catch (InterruptedException iie) { } + } + } + + public synchronized void notice(AltosState new_state, boolean spoken) { + AltosState old_state = state; + state = new_state; + if (!started && state.state > Altos.ao_flight_pad) { + started = true; + start(); + } + + if (state.state < Altos.ao_flight_drogue) + report_interval = 10000; + else + report_interval = 20000; + if (old_state != null && old_state.state != state.state) { + report_time = now(); + this.notify(); + } else if (spoken) + set_report_time(); + } + + public IdleThread() { + state = null; + reported_landing = 0; + report_interval = 10000; + } + } + + boolean tell(AltosState state, AltosState old_state) { + boolean ret = false; + if (old_state == null || old_state.state != state.state) { + voice.speak(state.data.state()); + if ((old_state == null || old_state.state <= Altos.ao_flight_boost) && + state.state > Altos.ao_flight_boost) { + voice.speak("max speed: %d meters per second.", + (int) (state.max_speed + 0.5)); + ret = true; + } else if ((old_state == null || old_state.state < Altos.ao_flight_drogue) && + state.state >= Altos.ao_flight_drogue) { + voice.speak("max height: %d meters.", + (int) (state.max_height + 0.5)); + ret = true; + } + } + if (old_state == null || old_state.gps_ready != state.gps_ready) { + if (state.gps_ready) { + voice.speak("GPS ready"); + ret = true; + } + else if (old_state != null) { + voice.speak("GPS lost"); + ret = true; + } + } + old_state = state; + return ret; + } + + public void run() { + boolean interrupted = false; + String line; + AltosState state = null; + AltosState old_state = null; + boolean told; + + idle_thread = new IdleThread(); + + display.reset(); + try { + for (;;) { + try { + AltosRecord record = reader.read(); + if (record == null) + break; + old_state = state; + state = new AltosState(record, state); + reader.update(state); + show(state, crc_errors); + told = tell(state, old_state); + idle_thread.notice(state, told); + } catch (ParseException pp) { + System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage()); + } catch (AltosCRCException ce) { + ++crc_errors; + show(state, crc_errors); + } + } + } catch (InterruptedException ee) { + interrupted = true; + } catch (IOException ie) { + JOptionPane.showMessageDialog(parent, + String.format("Error reading from \"%s\"", name), + "Telemetry Read Error", + JOptionPane.ERROR_MESSAGE); + } finally { + if (!interrupted) + idle_thread.report(true); + reader.close(interrupted); + idle_thread.interrupt(); + try { + idle_thread.join(); + } catch (InterruptedException ie) {} + } + } + + public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) { + parent = in_parent; + voice = in_voice; + display = in_display; + reader = in_reader; + } +} diff --git a/altosui/AltosEepromDownload.java b/altosui/AltosEepromDownload.java new file mode 100644 index 00000000..02fc36f2 --- /dev/null +++ b/altosui/AltosEepromDownload.java @@ -0,0 +1,285 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.*; + +import libaltosJNI.*; + +public class AltosEepromDownload implements Runnable { + + static final String[] state_names = { + "startup", + "idle", + "pad", + "boost", + "fast", + "coast", + "drogue", + "main", + "landed", + "invalid", + }; + + int[] ParseHex(String line) { + String[] tokens = line.split("\\s+"); + int[] array = new int[tokens.length]; + + for (int i = 0; i < tokens.length; i++) + try { + array[i] = Integer.parseInt(tokens[i], 16); + } catch (NumberFormatException ne) { + return null; + } + return array; + } + + int checksum(int[] line) { + int csum = 0x5a; + for (int i = 1; i < line.length; i++) + csum += line[i]; + return csum & 0xff; + } + + void FlushPending(FileWriter file, LinkedList<String> pending) throws IOException { + while (!pending.isEmpty()) { + file.write(pending.remove()); + } + } + + JFrame frame; + AltosDevice device; + AltosSerial serial_line; + boolean remote; + Thread eeprom_thread; + AltosEepromMonitor monitor; + + void CaptureLog() throws IOException, InterruptedException, TimeoutException { + int serial = 0; + int block, state_block = 0; + int addr; + int flight = 0; + int year = 0, month = 0, day = 0; + int state = 0; + boolean done = false; + boolean want_file = false; + boolean any_valid; + FileWriter eeprom_file = null; + AltosFile eeprom_name; + LinkedList<String> eeprom_pending = new LinkedList<String>(); + + serial_line.printf("\nc s\nv\n"); + + /* Pull the serial number out of the version information */ + + for (;;) { + String line = serial_line.get_reply(5000); + + if (line == null) + throw new TimeoutException(); + if (line.startsWith("serial-number")) { + try { + serial = Integer.parseInt(line.substring(13).trim()); + } catch (NumberFormatException ne) { + serial = 0; + } + } + + eeprom_pending.add(String.format("%s\n", line)); + + /* signals the end of the version info */ + if (line.startsWith("software-version")) + break; + } + if (serial == 0) + throw new IOException("no serial number found"); + + monitor.set_serial(serial); + /* Now scan the eeprom, reading blocks of data and converting to .eeprom file form */ + + state = 0; state_block = 0; + for (block = 0; !done && block < 511; block++) { + serial_line.printf("e %x\n", block); + any_valid = false; + monitor.set_value(state_names[state], state, block - state_block); + for (addr = 0; addr < 0x100;) { + String line = serial_line.get_reply(5000); + if (line == null) + throw new TimeoutException(); + int[] values = ParseHex(line); + + if (values == null) { + System.out.printf("invalid line: %s\n", line); + continue; + } else if (values[0] != addr) { + System.out.printf("data address out of sync at 0x%x\n", + block * 256 + values[0]); + } else if (checksum(values) != 0) { + System.out.printf("invalid checksum at 0x%x\n", + block * 256 + values[0]); + } else { + any_valid = true; + int cmd = values[1]; + int tick = values[3] + (values[4] << 8); + int a = values[5] + (values[6] << 8); + int b = values[7] + (values[8] << 8); + + if (cmd == Altos.AO_LOG_FLIGHT) { + flight = b; + monitor.set_flight(flight); + } + + /* Monitor state transitions to update display */ + if (cmd == Altos.AO_LOG_STATE && a <= Altos.ao_flight_landed) { + if (a > Altos.ao_flight_pad) + want_file = true; + if (a > state) + state_block = block; + state = a; + } + + if (cmd == Altos.AO_LOG_GPS_DATE) { + year = 2000 + (a & 0xff); + month = (a >> 8) & 0xff; + day = (b & 0xff); + want_file = true; + } + + if (eeprom_file == null) { + if (serial != 0 && flight != 0 && want_file) { + if (year != 0 && month != 0 && day != 0) + eeprom_name = new AltosFile(year, month, day, serial, flight, "eeprom"); + else + eeprom_name = new AltosFile(serial, flight, "eeprom"); + + monitor.set_file(eeprom_name.getName()); + eeprom_file = new FileWriter(eeprom_name); + if (eeprom_file != null) { + FlushPending(eeprom_file, eeprom_pending); + eeprom_pending = null; + } + } + } + + String log_line = String.format("%c %4x %4x %4x\n", + cmd, tick, a, b); + if (eeprom_file != null) + eeprom_file.write(log_line); + else + eeprom_pending.add(log_line); + + if (cmd == Altos.AO_LOG_STATE && a == Altos.ao_flight_landed) { + done = true; + } + } + addr += 8; + } + if (!any_valid) + done = true; + } + if (eeprom_file == null) { + eeprom_name = new AltosFile(serial,flight,"eeprom"); + eeprom_file = new FileWriter(eeprom_name); + if (eeprom_file != null) { + FlushPending(eeprom_file, eeprom_pending); + } + } + if (eeprom_file != null) { + eeprom_file.flush(); + eeprom_file.close(); + } + } + + public void run () { + if (remote) { + serial_line.set_radio(); + serial_line.printf("p\nE 0\n"); + serial_line.flush_input(); + } + + monitor = new AltosEepromMonitor(frame, Altos.ao_flight_boost, Altos.ao_flight_landed); + monitor.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + eeprom_thread.interrupt(); + } + }); + try { + CaptureLog(); + } catch (IOException ee) { + JOptionPane.showMessageDialog(frame, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + JOptionPane.showMessageDialog(frame, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + if (remote) + serial_line.printf("~"); + monitor.done(); + serial_line.flush_output(); + serial_line.close(); + } + + public AltosEepromDownload(JFrame given_frame) { + frame = given_frame; + device = AltosDeviceDialog.show(frame, AltosDevice.product_any); + + remote = false; + + if (device != null) { + try { + serial_line = new AltosSerial(device); + if (!device.matchProduct(AltosDevice.product_telemetrum)) + remote = true; + eeprom_thread = new Thread(this); + eeprom_thread.start(); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(frame, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(frame, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(frame, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/altosui/AltosEepromIterable.java b/altosui/AltosEepromIterable.java new file mode 100644 index 00000000..f8e6d7e5 --- /dev/null +++ b/altosui/AltosEepromIterable.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 java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * AltosRecords with an index field so they can be sorted by tick while preserving + * the original ordering for elements with matching ticks + */ +class AltosOrderedRecord extends AltosEepromRecord implements Comparable<AltosOrderedRecord> { + + public int index; + + public AltosOrderedRecord(String line, int in_index, int prev_tick, boolean prev_tick_valid) + throws ParseException { + super(line); + if (prev_tick_valid) { + tick |= (prev_tick & ~0xffff); + if (tick < prev_tick) { + if (prev_tick - tick > 0x8000) + tick += 0x10000; + } else { + if (tick - prev_tick > 0x8000) + tick -= 0x10000; + } + } + index = in_index; + } + + public AltosOrderedRecord(int in_cmd, int in_tick, int in_a, int in_b, int in_index) { + super(in_cmd, in_tick, in_a, in_b); + index = in_index; + } + + public int compareTo(AltosOrderedRecord o) { + int tick_diff = tick - o.tick; + if (tick_diff != 0) + return tick_diff; + return index - o.index; + } +} + +public class AltosEepromIterable extends AltosRecordIterable { + + static final int seen_flight = 1; + static final int seen_sensor = 2; + static final int seen_temp_volt = 4; + static final int seen_deploy = 8; + static final int seen_gps_time = 16; + static final int seen_gps_lat = 32; + static final int seen_gps_lon = 64; + + static final int seen_basic = seen_flight|seen_sensor|seen_temp_volt|seen_deploy; + + AltosEepromRecord flight_record; + AltosEepromRecord gps_date_record; + + TreeSet<AltosOrderedRecord> records; + + LinkedList<AltosRecord> list; + + class EepromState { + int seen; + int n_pad_samples; + double ground_pres; + int gps_tick; + int boost_tick; + + EepromState() { + seen = 0; + n_pad_samples = 0; + ground_pres = 0.0; + gps_tick = 0; + } + } + + void update_state(AltosRecord state, AltosEepromRecord record, EepromState eeprom) { + state.tick = record.tick; + switch (record.cmd) { + case Altos.AO_LOG_FLIGHT: + eeprom.seen |= seen_flight; + state.ground_accel = record.a; + state.flight_accel = record.a; + state.flight = record.b; + eeprom.boost_tick = record.tick; + break; + case Altos.AO_LOG_SENSOR: + state.accel = record.a; + state.pres = record.b; + if (state.state < Altos.ao_flight_boost) { + eeprom.n_pad_samples++; + eeprom.ground_pres += state.pres; + state.ground_pres = (int) (eeprom.ground_pres / eeprom.n_pad_samples); + state.flight_pres = state.ground_pres; + } else { + state.flight_pres = (state.flight_pres * 15 + state.pres) / 16; + state.flight_accel = (state.flight_accel * 15 + state.accel) / 16; + state.flight_vel += (state.accel_plus_g - state.accel); + } + eeprom.seen |= seen_sensor; + break; + case Altos.AO_LOG_TEMP_VOLT: + state.temp = record.a; + state.batt = record.b; + eeprom.seen |= seen_temp_volt; + break; + case Altos.AO_LOG_DEPLOY: + state.drogue = record.a; + state.main = record.b; + eeprom.seen |= seen_deploy; + break; + case Altos.AO_LOG_STATE: + state.state = record.a; + break; + case Altos.AO_LOG_GPS_TIME: + eeprom.gps_tick = state.tick; + AltosGPS old = state.gps; + state.gps = new AltosGPS(); + + /* GPS date doesn't get repeated through the file */ + if (old != null) { + state.gps.year = old.year; + state.gps.month = old.month; + state.gps.day = old.day; + } + state.gps.hour = (record.a & 0xff); + state.gps.minute = (record.a >> 8); + state.gps.second = (record.b & 0xff); + + int flags = (record.b >> 8); + state.gps.connected = (flags & Altos.AO_GPS_RUNNING) != 0; + state.gps.locked = (flags & Altos.AO_GPS_VALID) != 0; + state.gps.date_valid = (flags & Altos.AO_GPS_DATE_VALID) != 0; + state.gps.nsat = (flags & Altos.AO_GPS_NUM_SAT_MASK) >> + Altos.AO_GPS_NUM_SAT_SHIFT; + break; + case Altos.AO_LOG_GPS_LAT: + int lat32 = record.a | (record.b << 16); + state.gps.lat = (double) lat32 / 1e7; + break; + case Altos.AO_LOG_GPS_LON: + int lon32 = record.a | (record.b << 16); + state.gps.lon = (double) lon32 / 1e7; + break; + case Altos.AO_LOG_GPS_ALT: + state.gps.alt = record.a; + break; + case Altos.AO_LOG_GPS_SAT: + if (state.tick == eeprom.gps_tick) { + int svid = record.a; + int c_n0 = record.b >> 8; + state.gps.add_sat(svid, c_n0); + } + break; + case Altos.AO_LOG_GPS_DATE: + state.gps.year = (record.a & 0xff) + 2000; + state.gps.month = record.a >> 8; + state.gps.day = record.b & 0xff; + break; + + case Altos.AO_LOG_CONFIG_VERSION: + break; + case Altos.AO_LOG_MAIN_DEPLOY: + break; + case Altos.AO_LOG_APOGEE_DELAY: + break; + case Altos.AO_LOG_RADIO_CHANNEL: + break; + case Altos.AO_LOG_CALLSIGN: + state.callsign = record.data; + break; + case Altos.AO_LOG_ACCEL_CAL: + state.accel_plus_g = record.a; + state.accel_minus_g = record.b; + break; + case Altos.AO_LOG_RADIO_CAL: + break; + case Altos.AO_LOG_MANUFACTURER: + break; + case Altos.AO_LOG_PRODUCT: + break; + case Altos.AO_LOG_SERIAL_NUMBER: + state.serial = record.a; + break; + case Altos.AO_LOG_SOFTWARE_VERSION: + break; + } + } + + LinkedList<AltosRecord> make_list() { + LinkedList<AltosRecord> list = new LinkedList<AltosRecord>(); + Iterator<AltosOrderedRecord> iterator = records.iterator(); + AltosOrderedRecord record = null; + AltosRecord state = new AltosRecord(); + boolean last_reported = false; + EepromState eeprom = new EepromState(); + + state.state = Altos.ao_flight_pad; + state.accel_plus_g = 15758; + state.accel_minus_g = 16294; + + /* Pull in static data from the flight and gps_date records */ + if (flight_record != null) + update_state(state, flight_record, eeprom); + if (gps_date_record != null) + update_state(state, gps_date_record, eeprom); + + while (iterator.hasNext()) { + record = iterator.next(); + if ((eeprom.seen & seen_basic) == seen_basic && record.tick != state.tick) { + AltosRecord r = new AltosRecord(state); + r.time = (r.tick - eeprom.boost_tick) / 100.0; + list.add(r); + } + update_state(state, record, eeprom); + } + AltosRecord r = new AltosRecord(state); + r.time = (r.tick - eeprom.boost_tick) / 100.0; + list.add(r); + return list; + } + + public Iterator<AltosRecord> iterator() { + if (list == null) + list = make_list(); + return list.iterator(); + } + + public void write_comments(PrintStream out) { + Iterator<AltosOrderedRecord> iterator = records.iterator(); + out.printf("# Comments\n"); + while (iterator.hasNext()) { + AltosOrderedRecord record = iterator.next(); + switch (record.cmd) { + case Altos.AO_LOG_CONFIG_VERSION: + out.printf("# Config version: %s\n", record.data); + break; + case Altos.AO_LOG_MAIN_DEPLOY: + out.printf("# Main deploy: %s\n", record.a); + break; + case Altos.AO_LOG_APOGEE_DELAY: + out.printf("# Apogee delay: %s\n", record.a); + break; + case Altos.AO_LOG_RADIO_CHANNEL: + out.printf("# Radio channel: %s\n", record.a); + break; + case Altos.AO_LOG_CALLSIGN: + out.printf("# Callsign: %s\n", record.data); + break; + case Altos.AO_LOG_ACCEL_CAL: + out.printf ("# Accel cal: %d %d\n", record.a, record.b); + break; + case Altos.AO_LOG_RADIO_CAL: + out.printf ("# Radio cal: %d\n", record.a); + break; + case Altos.AO_LOG_MANUFACTURER: + out.printf ("# Manufacturer: %s\n", record.data); + break; + case Altos.AO_LOG_PRODUCT: + out.printf ("# Product: %s\n", record.data); + break; + case Altos.AO_LOG_SERIAL_NUMBER: + out.printf ("# Serial number: %d\n", record.a); + break; + case Altos.AO_LOG_SOFTWARE_VERSION: + out.printf ("# Software version: %s\n", record.data); + break; + } + } + } + + /* + * Given an AO_LOG_GPS_TIME record with correct time, and one + * missing time, rewrite the missing time values with the good + * ones, assuming that the difference between them is 'diff' seconds + */ + void update_time(AltosOrderedRecord good, AltosOrderedRecord bad) { + + int diff = (bad.tick - good.tick + 50) / 100; + + int hour = (good.a & 0xff); + int minute = (good.a >> 8); + int second = (good.b & 0xff); + int flags = (good.b >> 8); + int seconds = hour * 3600 + minute * 60 + second; + + /* Make sure this looks like a good GPS value */ + if ((flags & Altos.AO_GPS_NUM_SAT_MASK) >> Altos.AO_GPS_NUM_SAT_SHIFT < 4) + flags = (flags & ~Altos.AO_GPS_NUM_SAT_MASK) | (4 << Altos.AO_GPS_NUM_SAT_SHIFT); + flags |= Altos.AO_GPS_RUNNING; + flags |= Altos.AO_GPS_VALID; + + int new_seconds = seconds + diff; + if (new_seconds < 0) + new_seconds += 24 * 3600; + int new_second = (new_seconds % 60); + int new_minutes = (new_seconds / 60); + int new_minute = (new_minutes % 60); + int new_hours = (new_minutes / 60); + int new_hour = (new_hours % 24); + + bad.a = new_hour + (new_minute << 8); + bad.b = new_second + (flags << 8); + } + + /* + * Read the whole file, dumping records into a RB tree so + * we can enumerate them in time order -- the eeprom data + * are sometimes out of order with GPS data getting timestamps + * matching the first packet out of the GPS unit but not + * written until the final GPS packet has been received. + */ + public AltosEepromIterable (FileInputStream input) { + records = new TreeSet<AltosOrderedRecord>(); + + AltosOrderedRecord last_gps_time = null; + + int index = 0; + int prev_tick = 0; + boolean prev_tick_valid = false; + boolean missing_time = false; + + try { + for (;;) { + String line = AltosRecord.gets(input); + if (line == null) + break; + AltosOrderedRecord record = new AltosOrderedRecord(line, index++, prev_tick, prev_tick_valid); + if (record == null) + break; + if (record.cmd == Altos.AO_LOG_INVALID) + continue; + prev_tick = record.tick; + if (record.cmd < Altos.AO_LOG_CONFIG_VERSION) + prev_tick_valid = true; + if (record.cmd == Altos.AO_LOG_FLIGHT) { + flight_record = record; + continue; + } + + /* Two firmware bugs caused the loss of some GPS data. + * The flight date would never be recorded, and often + * the flight time would get overwritten by another + * record. Detect the loss of the GPS date and fix up the + * missing time records + */ + if (record.cmd == Altos.AO_LOG_GPS_DATE) { + gps_date_record = record; + continue; + } + + /* go back and fix up any missing time values */ + if (record.cmd == Altos.AO_LOG_GPS_TIME) { + last_gps_time = record; + if (missing_time) { + Iterator<AltosOrderedRecord> iterator = records.iterator(); + while (iterator.hasNext()) { + AltosOrderedRecord old = iterator.next(); + if (old.cmd == Altos.AO_LOG_GPS_TIME && + old.a == -1 && old.b == -1) + { + update_time(record, old); + } + } + missing_time = false; + } + } + + if (record.cmd == Altos.AO_LOG_GPS_LAT) { + if (last_gps_time == null || last_gps_time.tick != record.tick) { + AltosOrderedRecord add_gps_time = new AltosOrderedRecord(Altos.AO_LOG_GPS_TIME, + record.tick, + -1, -1, index-1); + if (last_gps_time != null) + update_time(last_gps_time, add_gps_time); + else + missing_time = true; + + records.add(add_gps_time); + record.index = index++; + } + } + records.add(record); + + /* Bail after reading the 'landed' record; we're all done */ + if (record.cmd == Altos.AO_LOG_STATE && + record.a == Altos.ao_flight_landed) + break; + } + } catch (IOException io) { + } catch (ParseException pe) { + } + try { + input.close(); + } catch (IOException ie) { + } + } +} diff --git a/altosui/AltosEepromMonitor.java b/altosui/AltosEepromMonitor.java new file mode 100644 index 00000000..7ff00ead --- /dev/null +++ b/altosui/AltosEepromMonitor.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.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosEepromMonitor extends JDialog { + + Container pane; + Box box; + JLabel serial_label; + JLabel flight_label; + JLabel file_label; + JLabel serial_value; + JLabel flight_value; + JLabel file_value; + JButton cancel; + JProgressBar pbar; + int min_state, max_state; + + public AltosEepromMonitor(JFrame owner, int in_min_state, int in_max_state) { + super (owner, "Download Flight Data", false); + + GridBagConstraints c; + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = 0; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + flight_label = new JLabel("Flight:"); + pane.add(flight_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + flight_value = new JLabel(""); + pane.add(flight_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 2; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + file_label = new JLabel("File:"); + pane.add(file_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 2; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + file_value = new JLabel(""); + pane.add(file_value, c); + + min_state = in_min_state; + max_state = in_max_state; + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum((max_state - min_state) * 100); + pbar.setValue(0); + pbar.setString("startup"); + pbar.setStringPainted(true); + pbar.setPreferredSize(new Dimension(600, 20)); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 3; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ib = new Insets(4,4,4,4); + c.insets = ib; + pane.add(pbar, c); + + + cancel = new JButton("Cancel"); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 4; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ic = new Insets(4,4,4,4); + c.insets = ic; + pane.add(cancel, c); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + public void addActionListener (ActionListener l) { + cancel.addActionListener(l); + } + + public void set_value(String state_name, int in_state, int in_block) { + int block = in_block; + int state = in_state; + + if (block > 100) + block = 100; + if (state < min_state) state = min_state; + if (state >= max_state) state = max_state - 1; + state -= min_state; + + int pos = state * 100 + block; + + pbar.setString(state_name); + pbar.setValue(pos); + } + + public void set_serial(int serial) { + serial_value.setText(String.format("%d", serial)); + } + + public void set_flight(int flight) { + flight_value.setText(String.format("%d", flight)); + } + + public void set_file(String file) { + file_value.setText(String.format("%s", file)); + } + + public void done() { + setVisible(false); + dispose(); + } +} diff --git a/altosui/AltosEepromRecord.java b/altosui/AltosEepromRecord.java new file mode 100644 index 00000000..5a673817 --- /dev/null +++ b/altosui/AltosEepromRecord.java @@ -0,0 +1,115 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosEepromRecord { + public int cmd; + public int tick; + public int a; + public int b; + public String data; + public boolean tick_valid; + + public AltosEepromRecord (String line) { + tick_valid = false; + tick = 0; + a = 0; + b = 0; + data = null; + if (line == null) { + cmd = Altos.AO_LOG_INVALID; + data = ""; + } else { + try { + String[] tokens = line.split("\\s+"); + + if (tokens[0].length() == 1) { + if (tokens.length != 4) { + cmd = Altos.AO_LOG_INVALID; + data = line; + } else { + cmd = tokens[0].codePointAt(0); + tick = Integer.parseInt(tokens[1],16); + tick_valid = true; + a = Integer.parseInt(tokens[2],16); + b = Integer.parseInt(tokens[3],16); + } + } else if (tokens[0].equals("Config") && tokens[1].equals("version:")) { + cmd = Altos.AO_LOG_CONFIG_VERSION; + data = tokens[2]; + } else if (tokens[0].equals("Main") && tokens[1].equals("deploy:")) { + cmd = Altos.AO_LOG_MAIN_DEPLOY; + a = Integer.parseInt(tokens[2]); + } else if (tokens[0].equals("Apogee") && tokens[1].equals("delay:")) { + cmd = Altos.AO_LOG_APOGEE_DELAY; + a = Integer.parseInt(tokens[2]); + } else if (tokens[0].equals("Radio") && tokens[1].equals("channel:")) { + cmd = Altos.AO_LOG_RADIO_CHANNEL; + a = Integer.parseInt(tokens[2]); + } else if (tokens[0].equals("Callsign:")) { + cmd = Altos.AO_LOG_CALLSIGN; + data = tokens[1].replaceAll("\"",""); + } else if (tokens[0].equals("Accel") && tokens[1].equals("cal")) { + cmd = Altos.AO_LOG_ACCEL_CAL; + a = Integer.parseInt(tokens[3]); + b = Integer.parseInt(tokens[5]); + } else if (tokens[0].equals("Radio") && tokens[1].equals("cal:")) { + cmd = Altos.AO_LOG_RADIO_CAL; + a = Integer.parseInt(tokens[2]); + } else if (tokens[0].equals("manufacturer")) { + cmd = Altos.AO_LOG_MANUFACTURER; + data = tokens[1]; + } else if (tokens[0].equals("product")) { + cmd = Altos.AO_LOG_PRODUCT; + data = tokens[1]; + } else if (tokens[0].equals("serial-number")) { + cmd = Altos.AO_LOG_SERIAL_NUMBER; + a = Integer.parseInt(tokens[1]); + } else if (tokens[0].equals("software-version")) { + cmd = Altos.AO_LOG_SOFTWARE_VERSION; + data = tokens[1]; + } else { + cmd = Altos.AO_LOG_INVALID; + data = line; + } + } catch (NumberFormatException ne) { + cmd = Altos.AO_LOG_INVALID; + data = line; + } + } + } + + public AltosEepromRecord(int in_cmd, int in_tick, int in_a, int in_b) { + tick_valid = true; + cmd = in_cmd; + tick = in_tick; + a = in_a; + b = in_b; + } +} diff --git a/altosui/AltosFile.java b/altosui/AltosFile.java new file mode 100644 index 00000000..06360572 --- /dev/null +++ b/altosui/AltosFile.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.File; +import java.util.*; + +class AltosFile extends File { + + public AltosFile(int year, int month, int day, int serial, int flight, String extension) { + super (AltosPreferences.logdir(), + String.format("%04d-%02d-%02d-serial-%03d-flight-%03d.%s", + year, month, day, serial, flight, extension)); + } + + public AltosFile(int serial, int flight, String extension) { + this(Calendar.getInstance().get(Calendar.YEAR), + Calendar.getInstance().get(Calendar.MONTH) + 1, + Calendar.getInstance().get(Calendar.DAY_OF_MONTH), + serial, + flight, + extension); + } + + public AltosFile(AltosTelemetry telem) { + this(telem.serial, telem.flight, "telem"); + } +} diff --git a/altosui/AltosFlash.java b/altosui/AltosFlash.java new file mode 100644 index 00000000..3af25c23 --- /dev/null +++ b/altosui/AltosFlash.java @@ -0,0 +1,344 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlash { + File file; + FileInputStream input; + AltosHexfile image; + JFrame frame; + AltosDevice debug_dongle; + AltosDebug debug; + AltosRomconfig rom_config; + ActionListener listener; + boolean aborted; + + static final byte MOV_direct_data = (byte) 0x75; + static final byte MOV_DPTR_data16 = (byte) 0x90; + static final byte MOV_A_data = (byte) 0x74; + static final byte MOVX_atDPTR_A = (byte) 0xf0; + static final byte MOVX_A_atDPTR = (byte) 0xe0; + static final byte INC_DPTR = (byte) 0xa3; + static final byte TRAP = (byte) 0xa5; + + static final byte JB = (byte) 0x20; + + static final byte MOV_A_direct = (byte) 0xe5; + static final byte MOV_direct1_direct2 = (byte) 0x85; + static final byte MOV_direct_A = (byte) 0xf5; + static final byte MOV_R0_data = (byte) (0x78 | 0); + static final byte MOV_R1_data = (byte) (0x78 | 1); + static final byte MOV_R2_data = (byte) (0x78 | 2); + static final byte MOV_R3_data = (byte) (0x78 | 3); + static final byte MOV_R4_data = (byte) (0x78 | 4); + static final byte MOV_R5_data = (byte) (0x78 | 5); + static final byte MOV_R6_data = (byte) (0x78 | 6); + static final byte MOV_R7_data = (byte) (0x78 | 7); + static final byte DJNZ_R0_rel = (byte) (0xd8 | 0); + static final byte DJNZ_R1_rel = (byte) (0xd8 | 1); + static final byte DJNZ_R2_rel = (byte) (0xd8 | 2); + static final byte DJNZ_R3_rel = (byte) (0xd8 | 3); + static final byte DJNZ_R4_rel = (byte) (0xd8 | 4); + static final byte DJNZ_R5_rel = (byte) (0xd8 | 5); + static final byte DJNZ_R6_rel = (byte) (0xd8 | 6); + static final byte DJNZ_R7_rel = (byte) (0xd8 | 7); + + static final byte P1DIR = (byte) 0xFE; + static final byte P1 = (byte) 0x90; + + /* flash controller */ + static final byte FWT = (byte) 0xAB; + static final byte FADDRL = (byte) 0xAC; + static final byte FADDRH = (byte) 0xAD; + static final byte FCTL = (byte) 0xAE; + static final byte FCTL_BUSY = (byte) 0x80; + static final byte FCTL_BUSY_BIT = (byte) 7; + static final byte FCTL_SWBSY = (byte) 0x40; + static final byte FCTL_SWBSY_BIT = (byte) 6; + static final byte FCTL_CONTRD = (byte) 0x10; + static final byte FCTL_WRITE = (byte) 0x02; + static final byte FCTL_ERASE = (byte) 0x01; + static final byte FWDATA = (byte) 0xAF; + + static final byte ACC = (byte) 0xE0; + + /* offsets within the flash_page program */ + static final int FLASH_ADDR_HIGH = 8; + static final int FLASH_ADDR_LOW = 11; + static final int RAM_ADDR_HIGH = 13; + static final int RAM_ADDR_LOW = 14; + static final int FLASH_WORDS_HIGH = 16; + static final int FLASH_WORDS_LOW = 18; + static final int FLASH_TIMING = 21; + + /* sleep mode control */ + static final int SLEEP = (byte) 0xbe; + static final int SLEEP_USB_EN = (byte) 0x80; + static final int SLEEP_XOSC_STB = (byte) 0x40; + static final int SLEEP_HFRC_STB = (byte) 0x20; + static final int SLEEP_RST_MASK = (byte) 0x18; + static final int SLEEP_RST_POWERON = (byte) 0x00; + static final int SLEEP_RST_EXTERNAL = (byte) 0x10; + static final int SLEEP_RST_WATCHDOG = (byte) 0x08; + static final int SLEEP_OSC_PD = (byte) 0x04; + static final int SLEEP_MODE_MASK = (byte) 0x03; + static final int SLEEP_MODE_PM0 = (byte) 0x00; + static final int SLEEP_MODE_PM1 = (byte) 0x01; + static final int SLEEP_MODE_PM2 = (byte) 0x02; + static final int SLEEP_MODE_PM3 = (byte) 0x03; + + /* clock controller */ + static final byte CLKCON = (byte) 0xC6; + static final byte CLKCON_OSC32K = (byte) 0x80; + static final byte CLKCON_OSC = (byte) 0x40; + static final byte CLKCON_TICKSPD = (byte) 0x38; + static final byte CLKCON_CLKSPD = (byte) 0x07; + + static final byte[] flash_page_proto = { + + MOV_direct_data, P1DIR, (byte) 0x02, + MOV_direct_data, P1, (byte) 0xFF, + + MOV_direct_data, FADDRH, 0, /* FLASH_ADDR_HIGH */ + + MOV_direct_data, FADDRL, 0, /* FLASH_ADDR_LOW */ + + MOV_DPTR_data16, 0, 0, /* RAM_ADDR_HIGH, RAM_ADDR_LOW */ + + MOV_R7_data, 0, /* FLASH_WORDS_HIGH */ + + MOV_R6_data, 0, /* FLASH_WORDS_LOW */ + + + MOV_direct_data, FWT, 0x20, /* FLASH_TIMING */ + + MOV_direct_data, FCTL, FCTL_ERASE, +/* eraseWaitLoop: */ + MOV_A_direct, FCTL, + JB, ACC|FCTL_BUSY_BIT, (byte) 0xfb, + + MOV_direct_data, P1, (byte) 0xfd, + + MOV_direct_data, FCTL, FCTL_WRITE, +/* writeLoop: */ + MOV_R5_data, 2, +/* writeWordLoop: */ + MOVX_A_atDPTR, + INC_DPTR, + MOV_direct_A, FWDATA, + DJNZ_R5_rel, (byte) 0xfa, /* writeWordLoop */ +/* writeWaitLoop: */ + MOV_A_direct, FCTL, + JB, ACC|FCTL_SWBSY_BIT, (byte) 0xfb, /* writeWaitLoop */ + DJNZ_R6_rel, (byte) 0xf1, /* writeLoop */ + DJNZ_R7_rel, (byte) 0xef, /* writeLoop */ + + MOV_direct_data, P1DIR, (byte) 0x00, + MOV_direct_data, P1, (byte) 0xFF, + TRAP, + }; + + public byte[] make_flash_page(int flash_addr, int ram_addr, int byte_count) { + int flash_word_addr = flash_addr >> 1; + int flash_word_count = ((byte_count + 1) >> 1); + + byte[] flash_page = new byte[flash_page_proto.length]; + for (int i = 0; i < flash_page.length; i++) + flash_page[i] = flash_page_proto[i]; + + flash_page[FLASH_ADDR_HIGH] = (byte) (flash_word_addr >> 8); + flash_page[FLASH_ADDR_LOW] = (byte) (flash_word_addr); + flash_page[RAM_ADDR_HIGH] = (byte) (ram_addr >> 8); + flash_page[RAM_ADDR_LOW] = (byte) (ram_addr); + + byte flash_words_low = (byte) (flash_word_count); + byte flash_words_high = (byte) (flash_word_count >> 8); + /* the flashing code has a minor 'bug' */ + if (flash_words_low != 0) + flash_words_high++; + + flash_page[FLASH_WORDS_HIGH] = (byte) flash_words_high; + flash_page[FLASH_WORDS_LOW] = (byte) flash_words_low; + return flash_page; + } + + static byte[] set_clkcon_fast = { + MOV_direct_data, CLKCON, 0x00 + }; + + static byte[] get_sleep = { + MOV_A_direct, SLEEP + }; + + public void clock_init() throws IOException, InterruptedException { + debug.debug_instr(set_clkcon_fast); + + byte status; + for (int times = 0; times < 20; times++) { + Thread.sleep(1); + status = debug.debug_instr(get_sleep); + if ((status & SLEEP_XOSC_STB) != 0) + return; + } + throw new IOException("Failed to initialize target clock"); + } + + void action(String s, int percent) { + if (listener != null && !aborted) + listener.actionPerformed(new ActionEvent(this, + percent, + s)); + } + + void action(int part, int total) { + int percent = 100 * part / total; + action(String.format("%d/%d (%d%%)", + part, total, percent), + percent); + } + + void run(int pc) throws IOException, InterruptedException { + debug.set_pc(pc); + int set_pc = debug.get_pc(); + if (pc != set_pc) + throw new IOException("Failed to set target program counter"); + debug.resume(); + + for (int times = 0; times < 20; times++) { + byte status = debug.read_status(); + if ((status & AltosDebug.STATUS_CPU_HALTED) != 0) + return; + } + + throw new IOException("Failed to execute program on target"); + } + + public void flash() throws IOException, FileNotFoundException, InterruptedException { + if (!check_rom_config()) + throw new IOException("Invalid rom config settings"); + if (image.address + image.data.length > 0x8000) + throw new IOException(String.format("Flash image too long %d", + image.address + + image.data.length)); + if ((image.address & 0x3ff) != 0) + throw new IOException(String.format("Flash image must start on page boundary (is 0x%x)", + image.address)); + int ram_address = 0xf000; + int flash_prog = 0xf400; + + /* + * Store desired config values into image + */ + rom_config.write(image); + /* + * Bring up the clock + */ + clock_init(); + + int remain = image.data.length; + int flash_addr = image.address; + int image_start = 0; + + action("start", 0); + action(0, image.data.length); + while (remain > 0 && !aborted) { + int this_time = remain; + if (this_time > 0x400) + this_time = 0x400; + + /* write the data */ + debug.write_memory(ram_address, image.data, + image_start, this_time); + + /* write the flash program */ + byte[] flash_page = make_flash_page(flash_addr, + ram_address, + this_time); + debug.write_memory(flash_prog, flash_page); + + run(flash_prog); + + byte[] check = debug.read_memory(flash_addr, this_time); + for (int i = 0; i < this_time; i++) + if (check[i] != image.data[image_start + i]) + throw new IOException(String.format("Flash write failed at 0x%x (%02x != %02x)", + image.address + image_start + i, + check[i], image.data[image_start + i])); + remain -= this_time; + flash_addr += this_time; + image_start += this_time; + + action(image.data.length - remain, image.data.length); + } + if (!aborted) { + action("done", 100); + debug.set_pc(image.address); + debug.resume(); + } + debug.close(); + } + + public void abort() { + aborted = true; + debug.close(); + } + + public void addActionListener(ActionListener l) { + listener = l; + } + + public boolean check_rom_config() { + if (rom_config == null) + rom_config = debug.romconfig(); + return rom_config != null && rom_config.valid(); + } + + public void set_romconfig (AltosRomconfig romconfig) { + rom_config = romconfig; + } + + public AltosRomconfig romconfig() { + if (!check_rom_config()) + return null; + return rom_config; + } + + public AltosFlash(File in_file, AltosDevice in_debug_dongle) + throws IOException, FileNotFoundException, AltosSerialInUseException, InterruptedException { + file = in_file; + debug_dongle = in_debug_dongle; + debug = new AltosDebug(in_debug_dongle); + input = new FileInputStream(file); + image = new AltosHexfile(input); + if (!debug.check_connection()) { + debug.close(); + throw new IOException("Debug port not connected"); + } + } +}
\ No newline at end of file diff --git a/altosui/AltosFlashUI.java b/altosui/AltosFlashUI.java new file mode 100644 index 00000000..f63097ac --- /dev/null +++ b/altosui/AltosFlashUI.java @@ -0,0 +1,218 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlashUI + extends JDialog + implements Runnable, ActionListener +{ + Container pane; + Box box; + JLabel serial_label; + JLabel serial_value; + JLabel file_label; + JLabel file_value; + JProgressBar pbar; + JButton cancel; + + File file; + Thread thread; + JFrame frame; + AltosDevice debug_dongle; + AltosFlash flash; + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == cancel) { + abort(); + dispose(); + } else { + String cmd = e.getActionCommand(); + if (cmd.equals("done")) + ; + else if (cmd.equals("start")) { + setVisible(true); + } else { + pbar.setValue(e.getID()); + pbar.setString(cmd); + } + } + } + + public void run() { + try { + flash = new AltosFlash(file, debug_dongle); + flash.addActionListener(this); + AltosRomconfigUI romconfig_ui = new AltosRomconfigUI (frame); + + romconfig_ui.set(flash.romconfig()); + AltosRomconfig romconfig = romconfig_ui.showDialog(); + + if (romconfig != null && romconfig.valid()) { + flash.set_romconfig(romconfig); + serial_value.setText(String.format("%d", + flash.romconfig().serial_number)); + file_value.setText(file.toString()); + setVisible(true); + flash.flash(); + flash = null; + } + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(frame, + "Cannot open image", + file.toString(), + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(frame, + String.format("Device \"%s\" already in use", + debug_dongle.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException e) { + JOptionPane.showMessageDialog(frame, + e.getMessage(), + file.toString(), + JOptionPane.ERROR_MESSAGE); + } catch (InterruptedException ie) { + } finally { + abort(); + } + dispose(); + } + + public void abort() { + if (flash != null) + flash.abort(); + } + + public void build_dialog() { + GridBagConstraints c; + Insets il = new Insets(4,4,4,4); + Insets ir = new Insets(4,4,4,4); + + pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = 0; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + serial_label = new JLabel("Serial:"); + pane.add(serial_label, c); + + c = new GridBagConstraints(); + c.gridx = 1; c.gridy = 0; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + serial_value = new JLabel(""); + pane.add(serial_value, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.gridx = 0; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = il; + file_label = new JLabel("File:"); + pane.add(file_label, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 1; c.gridy = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + file_value = new JLabel(""); + pane.add(file_value, c); + + pbar = new JProgressBar(); + pbar.setMinimum(0); + pbar.setMaximum(100); + pbar.setValue(0); + pbar.setString(""); + pbar.setStringPainted(true); + pbar.setPreferredSize(new Dimension(600, 20)); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 2; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ib = new Insets(4,4,4,4); + c.insets = ib; + pane.add(pbar, c); + + cancel = new JButton("Cancel"); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.CENTER; + c.gridx = 0; c.gridy = 3; + c.gridwidth = GridBagConstraints.REMAINDER; + Insets ic = new Insets(4,4,4,4); + c.insets = ic; + pane.add(cancel, c); + cancel.addActionListener(this); + pack(); + setLocationRelativeTo(frame); + } + + public AltosFlashUI(JFrame in_frame) { + super(in_frame, "Program Altusmetrum Device", false); + + frame = in_frame; + + build_dialog(); + + debug_dongle = AltosDeviceDialog.show(frame, AltosDevice.product_any); + + if (debug_dongle == null) + return; + + JFileChooser hexfile_chooser = new JFileChooser(); + + File firmwaredir = AltosPreferences.firmwaredir(); + if (firmwaredir != null) + hexfile_chooser.setCurrentDirectory(firmwaredir); + + hexfile_chooser.setDialogTitle("Select Flash Image"); + hexfile_chooser.setFileFilter(new FileNameExtensionFilter("Flash Image", "ihx")); + int returnVal = hexfile_chooser.showOpenDialog(frame); + + if (returnVal != JFileChooser.APPROVE_OPTION) + return; + + file = hexfile_chooser.getSelectedFile(); + + if (file != null) + AltosPreferences.set_firmwaredir(file.getParentFile()); + + thread = new Thread(this); + thread.start(); + } +}
\ No newline at end of file diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java new file mode 100644 index 00000000..d18d1d1f --- /dev/null +++ b/altosui/AltosFlightDisplay.java @@ -0,0 +1,24 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public interface AltosFlightDisplay { + void reset(); + + void show(AltosState state, int crc_errors); +} diff --git a/altosui/AltosFlightInfoTableModel.java b/altosui/AltosFlightInfoTableModel.java new file mode 100644 index 00000000..e23eff68 --- /dev/null +++ b/altosui/AltosFlightInfoTableModel.java @@ -0,0 +1,84 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightInfoTableModel extends AbstractTableModel { + final static private String[] columnNames = {"Field", "Value"}; + + int rows; + int cols; + private String[][] data; + + public int getColumnCount() { return cols; } + public int getRowCount() { return rows; } + public String getColumnName(int col) { return columnNames[col & 1]; } + + public Object getValueAt(int row, int col) { + if (row >= rows || col >= cols) + return ""; + return data[row][col]; + } + + int[] current_row; + + public void reset() { + for (int i = 0; i < cols / 2; i++) + current_row[i] = 0; + } + + public void clear() { + reset(); + for (int c = 0; c < cols; c++) + for (int r = 0; r < rows; r++) + data[r][c] = ""; + fireTableDataChanged(); + } + + public void addRow(int col, String name, String value) { + if (current_row[col] < rows) { + data[current_row[col]][col * 2] = name; + data[current_row[col]][col * 2 + 1] = value; + } + current_row[col]++; + } + + public void finish() { + for (int c = 0; c < cols / 2; c++) + while (current_row[c] < rows) + addRow(c, "", ""); + fireTableDataChanged(); + } + + public AltosFlightInfoTableModel (int in_rows, int in_cols) { + rows = in_rows; + cols = in_cols * 2; + data = new String[rows][cols]; + current_row = new int[in_cols]; + } +} diff --git a/altosui/AltosFlightReader.java b/altosui/AltosFlightReader.java new file mode 100644 index 00000000..3d59de9a --- /dev/null +++ b/altosui/AltosFlightReader.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.text.*; +import java.io.*; + +public class AltosFlightReader { + String name; + + int serial; + + void init() { } + + AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; } + + void close(boolean interrupted) { } + + void set_channel(int channel) { } + + void update(AltosState state) throws InterruptedException { } +} diff --git a/altosui/AltosFlightStatus.java b/altosui/AltosFlightStatus.java new file mode 100644 index 00000000..59c9e9db --- /dev/null +++ b/altosui/AltosFlightStatus.java @@ -0,0 +1,154 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightStatus extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + + public class FlightValue { + JLabel label; + JTextField value; + + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + public FlightValue (GridBagLayout layout, int x, String text) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(5, 5, 5, 5); + c.anchor = GridBagConstraints.CENTER; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + + label = new JLabel(text); + label.setFont(Altos.status_font); + label.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 0; + layout.setConstraints(label, c); + add(label); + + value = new JTextField(""); + value.setFont(Altos.status_font); + value.setHorizontalAlignment(SwingConstants.CENTER); + c.gridx = x; c.gridy = 1; + layout.setConstraints(value, c); + add(value); + } + } + + class Call extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(state.data.callsign); + } + public Call (GridBagLayout layout, int x) { + super (layout, x, "Callsign"); + } + } + + Call call; + + class Serial extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.serial)); + } + public Serial (GridBagLayout layout, int x) { + super (layout, x, "Serial"); + } + } + + Serial serial; + + class Flight extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.flight)); + } + public Flight (GridBagLayout layout, int x) { + super (layout, x, "Flight"); + } + } + + Flight flight; + + class FlightState extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(state.data.state()); + } + public FlightState (GridBagLayout layout, int x) { + super (layout, x, "State"); + } + } + + FlightState flight_state; + + class RSSI extends FlightValue { + void show(AltosState state, int crc_errors) { + value.setText(String.format("%d", state.data.rssi)); + } + public RSSI (GridBagLayout layout, int x) { + super (layout, x, "RSSI (dBm)"); + } + } + + RSSI rssi; + + public void reset () { + call.reset(); + serial.reset(); + flight.reset(); + flight_state.reset(); + rssi.reset(); + } + + public void show (AltosState state, int crc_errors) { + call.show(state, crc_errors); + serial.show(state, crc_errors); + flight.show(state, crc_errors); + flight_state.show(state, crc_errors); + rssi.show(state, crc_errors); + } + + public int height() { + Dimension d = layout.preferredLayoutSize(this); + return d.height; + } + + public AltosFlightStatus() { + layout = new GridBagLayout(); + + setLayout(layout); + + call = new Call(layout, 0); + serial = new Serial(layout, 1); + flight = new Flight(layout, 2); + flight_state = new FlightState(layout, 3); + rssi = new RSSI(layout, 4); + } +} diff --git a/altosui/AltosFlightStatusTableModel.java b/altosui/AltosFlightStatusTableModel.java new file mode 100644 index 00000000..4c24b6ac --- /dev/null +++ b/altosui/AltosFlightStatusTableModel.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightStatusTableModel extends AbstractTableModel { + private String[] columnNames = {"Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" }; + private Object[] data = { 0, "idle", 0, 0 }; + + public int getColumnCount() { return columnNames.length; } + public int getRowCount() { return 2; } + public Object getValueAt(int row, int col) { + if (row == 0) + return columnNames[col]; + return data[col]; + } + + public void setValueAt(Object value, int col) { + data[col] = value; + fireTableCellUpdated(1, col); + } + + public void setValueAt(Object value, int row, int col) { + setValueAt(value, col); + } + + public void set(AltosState state) { + setValueAt(String.format("%1.0f", state.height), 0); + setValueAt(state.data.state(), 1); + setValueAt(state.data.rssi, 2); + double speed = state.baro_speed; + if (state.ascent) + speed = state.speed; + setValueAt(String.format("%1.0f", speed), 3); + } +} diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java new file mode 100644 index 00000000..24d25bd7 --- /dev/null +++ b/altosui/AltosFlightUI.java @@ -0,0 +1,221 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosFlightUI extends JFrame implements AltosFlightDisplay { + String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" }; + Object[][] statusData = { { "0", "pad", "-50", "0" } }; + + AltosVoice voice; + AltosFlightReader reader; + AltosDisplayThread thread; + + JTabbedPane pane; + + AltosPad pad; + AltosAscent ascent; + AltosDescent descent; + AltosLanded landed; + AltosSiteMap sitemap; + + private AltosFlightStatus flightStatus; + private AltosInfoTable flightInfo; + + static final int tab_pad = 1; + static final int tab_ascent = 2; + static final int tab_descent = 3; + static final int tab_landed = 4; + + int cur_tab = 0; + + boolean exit_on_close = false; + + int which_tab(AltosState state) { + if (state.state < Altos.ao_flight_boost) + return tab_pad; + if (state.state <= Altos.ao_flight_coast) + return tab_ascent; + if (state.state <= Altos.ao_flight_main) + return tab_descent; + return tab_landed; + } + + void stop_display() { + if (thread != null && thread.isAlive()) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ie) {} + } + thread = null; + } + + void disconnect() { + stop_display(); + } + + public void reset() { + pad.reset(); + ascent.reset(); + descent.reset(); + landed.reset(); + flightInfo.clear(); + sitemap.reset(); + } + + public void show(AltosState state, int crc_errors) { + int tab = which_tab(state); + pad.show(state, crc_errors); + ascent.show(state, crc_errors); + descent.show(state, crc_errors); + landed.show(state, crc_errors); + if (tab != cur_tab) { + switch (tab) { + case tab_pad: + pane.setSelectedComponent(pad); + break; + case tab_ascent: + pane.setSelectedComponent(ascent); + break; + case tab_descent: + pane.setSelectedComponent(descent); + break; + case tab_landed: + pane.setSelectedComponent(landed); + } + cur_tab = tab; + } + flightStatus.show(state, crc_errors); + flightInfo.show(state, crc_errors); + sitemap.show(state, crc_errors); + } + + public void set_exit_on_close() { + exit_on_close = true; + } + + Container bag; + JComboBox channels; + + public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) { + AltosPreferences.init(this); + + voice = in_voice; + reader = in_reader; + + bag = getContentPane(); + bag.setLayout(new GridBagLayout()); + + GridBagConstraints c = new GridBagConstraints(); + + java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg"); + if (imgURL != null) + setIconImage(new ImageIcon(imgURL).getImage()); + + setTitle(String.format("AltOS %s", reader.name)); + + /* Stick channel selector at top of table for telemetry monitoring */ + if (serial >= 0) { + // Channel menu + channels = new AltosChannelMenu(AltosPreferences.channel(serial)); + channels.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int channel = channels.getSelectedIndex(); + reader.set_channel(channel); + } + }); + c.gridx = 0; + c.gridy = 0; + c.anchor = GridBagConstraints.WEST; + bag.add (channels, c); + } + + /* Flight status is always visible */ + flightStatus = new AltosFlightStatus(); + c.gridx = 0; + c.gridy = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + bag.add(flightStatus, c); + + /* The rest of the window uses a tabbed pane to + * show one of the alternate data views + */ + pane = new JTabbedPane(); + + pad = new AltosPad(); + pane.add("Launch Pad", pad); + + ascent = new AltosAscent(); + pane.add("Ascent", ascent); + + descent = new AltosDescent(); + pane.add("Descent", descent); + + landed = new AltosLanded(); + pane.add("Landed", landed); + + flightInfo = new AltosInfoTable(); + pane.add("Table", new JScrollPane(flightInfo)); + + sitemap = new AltosSiteMap(); + pane.add("Site Map", sitemap); + + /* Make the tabbed pane use the rest of the window space */ + c.gridx = 0; + c.gridy = 2; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1; + c.weighty = 1; + bag.add(pane, c); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + disconnect(); + setVisible(false); + dispose(); + if (exit_on_close) + System.exit(0); + } + }); + + pack(); + setVisible(true); + + thread = new AltosDisplayThread(this, voice, this, reader); + + thread.start(); + } + + public AltosFlightUI (AltosVoice in_voice, AltosFlightReader in_reader) { + this(in_voice, in_reader, -1); + } +} diff --git a/altosui/AltosGPS.java b/altosui/AltosGPS.java new file mode 100644 index 00000000..83821842 --- /dev/null +++ b/altosui/AltosGPS.java @@ -0,0 +1,215 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.text.*; + +public class AltosGPS { + public class AltosGPSSat { + int svid; + int c_n0; + } + + int nsat; + boolean locked; + boolean connected; + boolean date_valid; + double lat; /* degrees (+N -S) */ + double lon; /* degrees (+E -W) */ + int alt; /* m */ + int year; + int month; + int day; + int hour; + int minute; + int second; + + int gps_extended; /* has extra data */ + double ground_speed; /* m/s */ + int course; /* degrees */ + double climb_rate; /* m/s */ + double hdop; /* unitless? */ + int h_error; /* m */ + int v_error; /* m */ + + AltosGPSSat[] cc_gps_sat; /* tracking data */ + + void ParseGPSDate(String date) throws ParseException { + String[] ymd = date.split("-"); + if (ymd.length != 3) + throw new ParseException("error parsing GPS date " + date + " got " + ymd.length, 0); + year = AltosParse.parse_int(ymd[0]); + month = AltosParse.parse_int(ymd[1]); + day = AltosParse.parse_int(ymd[2]); + } + + void ParseGPSTime(String time) throws ParseException { + String[] hms = time.split(":"); + if (hms.length != 3) + throw new ParseException("Error parsing GPS time " + time + " got " + hms.length, 0); + hour = AltosParse.parse_int(hms[0]); + minute = AltosParse.parse_int(hms[1]); + second = AltosParse.parse_int(hms[2]); + } + + void ClearGPSTime() { + year = month = day = 0; + hour = minute = second = 0; + } + + public AltosGPS(String[] words, int i, int version) throws ParseException { + AltosParse.word(words[i++], "GPS"); + nsat = AltosParse.parse_int(words[i++]); + AltosParse.word(words[i++], "sat"); + + connected = false; + locked = false; + lat = lon = 0; + alt = 0; + ClearGPSTime(); + if ((words[i]).equals("unlocked")) { + connected = true; + i++; + } else if ((words[i]).equals("not-connected")) { + i++; + } else if (words.length >= 40) { + locked = true; + connected = true; + + if (version > 1) + ParseGPSDate(words[i++]); + else + year = month = day = 0; + ParseGPSTime(words[i++]); + lat = AltosParse.parse_coord(words[i++]); + lon = AltosParse.parse_coord(words[i++]); + alt = AltosParse.parse_int(words[i++]); + if (version > 1 || (i < words.length && !words[i].equals("SAT"))) { + ground_speed = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "m/s(H)")); + course = AltosParse.parse_int(words[i++]); + climb_rate = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "m/s(V)")); + hdop = AltosParse.parse_double(AltosParse.strip_suffix(words[i++], "(hdop)")); + h_error = AltosParse.parse_int(words[i++]); + v_error = AltosParse.parse_int(words[i++]); + } + } else { + i++; + } + if (i < words.length) { + AltosParse.word(words[i++], "SAT"); + int tracking_channels = 0; + if (words[i].equals("not-connected")) + tracking_channels = 0; + else + tracking_channels = AltosParse.parse_int(words[i]); + i++; + cc_gps_sat = new AltosGPS.AltosGPSSat[tracking_channels]; + for (int chan = 0; chan < tracking_channels; chan++) { + cc_gps_sat[chan] = new AltosGPS.AltosGPSSat(); + cc_gps_sat[chan].svid = AltosParse.parse_int(words[i++]); + /* Older versions included SiRF status bits */ + if (version < 2) + i++; + cc_gps_sat[chan].c_n0 = AltosParse.parse_int(words[i++]); + } + } else + cc_gps_sat = new AltosGPS.AltosGPSSat[0]; + } + + public void set_latitude(int in_lat) { + lat = in_lat / 10.0e7; + } + + public void set_longitude(int in_lon) { + lon = in_lon / 10.0e7; + } + + public void set_time(int hour, int minute, int second) { + hour = hour; + minute = minute; + second = second; + } + + public void set_date(int year, int month, int day) { + year = year; + month = month; + day = day; + } + + public void set_flags(int flags) { + flags = flags; + } + + public void set_altitude(int altitude) { + altitude = altitude; + } + + public void add_sat(int svid, int c_n0) { + if (cc_gps_sat == null) { + cc_gps_sat = new AltosGPS.AltosGPSSat[1]; + } else { + AltosGPSSat[] new_gps_sat = new AltosGPS.AltosGPSSat[cc_gps_sat.length + 1]; + for (int i = 0; i < cc_gps_sat.length; i++) + new_gps_sat[i] = cc_gps_sat[i]; + cc_gps_sat = new_gps_sat; + } + AltosGPS.AltosGPSSat sat = new AltosGPS.AltosGPSSat(); + sat.svid = svid; + sat.c_n0 = c_n0; + cc_gps_sat[cc_gps_sat.length - 1] = sat; + } + + public AltosGPS() { + ClearGPSTime(); + cc_gps_sat = null; + } + + public AltosGPS(AltosGPS old) { + nsat = old.nsat; + locked = old.locked; + connected = old.connected; + date_valid = old.date_valid; + lat = old.lat; /* degrees (+N -S) */ + lon = old.lon; /* degrees (+E -W) */ + alt = old.alt; /* m */ + year = old.year; + month = old.month; + day = old.day; + hour = old.hour; + minute = old.minute; + second = old.second; + + gps_extended = old.gps_extended; /* has extra data */ + ground_speed = old.ground_speed; /* m/s */ + course = old.course; /* degrees */ + climb_rate = old.climb_rate; /* m/s */ + hdop = old.hdop; /* unitless? */ + h_error = old.h_error; /* m */ + v_error = old.v_error; /* m */ + + if (old.cc_gps_sat != null) { + cc_gps_sat = new AltosGPSSat[old.cc_gps_sat.length]; + for (int i = 0; i < old.cc_gps_sat.length; i++) { + cc_gps_sat[i] = new AltosGPSSat(); + cc_gps_sat[i].svid = old.cc_gps_sat[i].svid; + cc_gps_sat[i].c_n0 = old.cc_gps_sat[i].c_n0; + } + } + } +} diff --git a/altosui/AltosGraph.java b/altosui/AltosGraph.java new file mode 100644 index 00000000..58c27979 --- /dev/null +++ b/altosui/AltosGraph.java @@ -0,0 +1,25 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.*; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.ChartUtilities; + +abstract class AltosGraph { + public String filename; + public abstract void addData(AltosDataPoint d); + public abstract JFreeChart createChart(); + 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..a5451280 --- /dev/null +++ b/altosui/AltosGraphTime.java @@ -0,0 +1,233 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.HashMap; + +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 double val = Double.NaN; + private String name; + private int state; + + 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 (Double.isNaN(val) || time < val) { + if (d.state() == state) { + val = time; + } + } + } + + public void addToPlot(AltosGraphTime g, XYPlot plot) { + if (Double.isNaN(val)) + return; + + ValueMarker m = new ValueMarker(val); + 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 String title; + 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..cd158651 --- /dev/null +++ b/altosui/AltosGraphUI.java @@ -0,0 +1,274 @@ + +// Copyright (c) 2010 Anthony Towns +// GPL v2 or later + +package altosui; + +import java.io.*; +import java.util.ArrayList; + +import javax.swing.JFrame; +import java.awt.Color; + +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 JFrame +{ + 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 private class OverallGraphs { + AltosGraphTime.Element height = + new AltosGraphTime.TimeSeries("Height (m)", "Height (AGL)", red) { + public void gotTimeData(double time, AltosDataPoint d) { + series.add(time, d.height()); + } + }; + + AltosGraphTime.Element speed = + new AltosGraphTime.TimeSeries("Speed (m/s)", "Vertical Speed", green) { + public void gotTimeData(double time, AltosDataPoint d) { + if (d.state() < Altos.ao_flight_drogue) { + series.add(time, d.accel_speed()); + } else { + series.add(time, d.baro_speed()); + } + } + }; + + AltosGraphTime.Element acceleration = + new AltosGraphTime.TimeSeries("Acceleration (m/s\u00B2)", + "Axial Acceleration", blue) + { + public void gotTimeData(double time, AltosDataPoint d) { + series.add(time, d.acceleration()); + } + }; + + AltosGraphTime.Element temperature = + new AltosGraphTime.TimeSeries("Temperature (\u00B0C)", + "Board temperature", red) + { + public void gotTimeData(double time, AltosDataPoint d) { + series.add(time, d.temperature()); + } + }; + + AltosGraphTime.Element drogue_voltage = + new AltosGraphTime.TimeSeries("Voltage (V)", "Drogue Continuity", blue) + { + public void gotTimeData(double time, AltosDataPoint d) { + series.add(time, d.drogue_voltage()); + } + }; + + AltosGraphTime.Element main_voltage = + new AltosGraphTime.TimeSeries("Voltage (V)", "Main Continuity", green) + { + public void gotTimeData(double time, AltosDataPoint d) { + series.add(time, d.main_voltage()); + } + }; + + 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_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("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) { + super("Altos Graph"); + + Iterable<AltosDataPoint> reader = new AltosDataPointReader (records); + if (reader == null) + return; + + init(reader, 0); + } + + public AltosGraphUI(Iterable<AltosDataPoint> data, int which) + { + super("Altos Graph"); + init(data, which); + } + + private void init(Iterable<AltosDataPoint> data, int which) { + 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)); + setContentPane(chartPanel); + + 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/AltosGreatCircle.java b/altosui/AltosGreatCircle.java new file mode 100644 index 00000000..fb1b6ab3 --- /dev/null +++ b/altosui/AltosGreatCircle.java @@ -0,0 +1,100 @@ +/* + * 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.Math; + +public class AltosGreatCircle { + double distance; + double bearing; + + double sqr(double a) { return a * a; } + + static final double rad = Math.PI / 180; + static final double earth_radius = 6371.2 * 1000; /* in meters */ + + static int BEARING_LONG = 0; + static int BEARING_SHORT = 1; + static int BEARING_VOICE = 2; + String bearing_words(int length) { + String [][] bearing_string = { + { + "North", "North North East", "North East", "East North East", + "East", "East South East", "South East", "South South East", + "South", "South South West", "South West", "West South West", + "West", "West North West", "North West", "North North West" + }, { + "N", "NNE", "NE", "ENE", + "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", + "W", "WNW", "NW", "NNW" + }, { + "north", "nor nor east", "north east", "east nor east", + "east", "east sow east", "south east", "sow sow east", + "south", "sow sow west", "south west", "west sow west", + "west", "west nor west", "north west", "nor nor west " + } + }; + return bearing_string[length][(int)((bearing / 90 * 8 + 1) / 2)%16]; + } + + public AltosGreatCircle (double start_lat, double start_lon, + double end_lat, double end_lon) + { + double lat1 = rad * start_lat; + double lon1 = rad * -start_lon; + double lat2 = rad * end_lat; + double lon2 = rad * -end_lon; + + double d_lon = lon2 - lon1; + + /* From http://en.wikipedia.org/wiki/Great-circle_distance */ + double vdn = Math.sqrt(sqr(Math.cos(lat2) * Math.sin(d_lon)) + + sqr(Math.cos(lat1) * Math.sin(lat2) - + Math.sin(lat1) * Math.cos(lat2) * Math.cos(d_lon))); + double vdd = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(d_lon); + double d = Math.atan2(vdn,vdd); + double course; + + if (Math.cos(lat1) < 1e-20) { + if (lat1 > 0) + course = Math.PI; + else + course = -Math.PI; + } else { + if (d < 1e-10) + course = 0; + else + course = Math.acos((Math.sin(lat2)-Math.sin(lat1)*Math.cos(d)) / + (Math.sin(d)*Math.cos(lat1))); + if (Math.sin(lon2-lon1) > 0) + course = 2 * Math.PI-course; + } + distance = d * earth_radius; + bearing = course * 180/Math.PI; + } + + public AltosGreatCircle(AltosGPS start, AltosGPS end) { + this(start.lat, start.lon, end.lat, end.lon); + } + + public AltosGreatCircle() { + distance = 0; + bearing = 0; + } +} diff --git a/altosui/AltosHexfile.java b/altosui/AltosHexfile.java new file mode 100644 index 00000000..19e35ae1 --- /dev/null +++ b/altosui/AltosHexfile.java @@ -0,0 +1,252 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.LinkedList; +import java.util.Iterator; +import java.util.Arrays; + +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/AltosIgnite.java b/altosui/AltosIgnite.java new file mode 100644 index 00000000..3cbd8a75 --- /dev/null +++ b/altosui/AltosIgnite.java @@ -0,0 +1,173 @@ +/* + * 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.*; + +public class AltosIgnite { + AltosDevice device; + AltosSerial serial; + boolean remote; + boolean serial_started; + final static int None = 0; + final static int Apogee = 1; + final static int Main = 2; + + final static int Unknown = 0; + final static int Ready = 1; + final static int Active = 2; + final static int Open = 3; + + private void start_serial() throws InterruptedException { + serial_started = true; + if (remote) { + serial.set_radio(); + serial.printf("p\nE 0\n"); + serial.flush_input(); + } + } + + private void stop_serial() throws InterruptedException { + if (!serial_started) + return; + serial_started = false; + if (serial == null) + return; + if (remote) { + serial.printf("~"); + serial.flush_output(); + } + } + + 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; + } + } + + private int status(String status_name) { + if (status_name.equals("unknown")) + return Unknown; + if (status_name.equals("ready")) + return Ready; + if (status_name.equals("active")) + return Active; + if (status_name.equals("open")) + return Open; + return Unknown; + } + + public int status(int igniter) throws InterruptedException, TimeoutException { + int status = Unknown; + if (serial == null) + return status; + string_ref status_name = new string_ref(); + start_serial(); + serial.printf("t\n"); + for (;;) { + String line = serial.get_reply(5000); + if (line == null) + throw new TimeoutException(); + if (get_string(line, "Igniter: drogue Status: ", status_name)) + if (igniter == Apogee) + status = status(status_name.get()); + if (get_string(line, "Igniter: main Status: ", status_name)) { + if (igniter == Main) + status = status(status_name.get()); + break; + } + } + stop_serial(); + return status; + } + + public String status_string(int status) { + switch (status) { + case Unknown: return "Unknown"; + case Ready: return "Ready"; + case Active: return "Active"; + case Open: return "Open"; + default: return "Unknown"; + } + } + + public void fire(int igniter) { + if (serial == null) + return; + try { + start_serial(); + switch (igniter) { + case Main: + serial.printf("i DoIt main\n"); + break; + case Apogee: + serial.printf("i DoIt drogue\n"); + break; + } + } catch (InterruptedException ie) { + } finally { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + } + } + + public void close() { + try { + stop_serial(); + } catch (InterruptedException ie) { + } + serial.close(); + serial = null; + } + + public AltosIgnite(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + + device = in_device; + serial = new AltosSerial(device); + remote = false; + + if (!device.matchProduct(AltosDevice.product_telemetrum)) + remote = true; + } +}
\ No newline at end of file diff --git a/altosui/AltosIgniteUI.java b/altosui/AltosIgniteUI.java new file mode 100644 index 00000000..d542729c --- /dev/null +++ b/altosui/AltosIgniteUI.java @@ -0,0 +1,317 @@ +/* + * 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.*; + +public class AltosIgniteUI + extends JDialog + implements ActionListener +{ + AltosDevice device; + AltosIgnite ignite; + JFrame owner; + JLabel label; + JRadioButton apogee; + JLabel apogee_status_label; + JRadioButton main; + JLabel main_status_label; + JToggleButton arm; + JButton fire; + javax.swing.Timer timer; + + int apogee_status; + int main_status; + + final static int timeout = 1 * 1000; + + int time_remaining; + boolean timer_running; + + 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 get_ignite_status() throws InterruptedException, TimeoutException { + apogee_status = ignite.status(AltosIgnite.Apogee); + main_status = ignite.status(AltosIgnite.Main); + } + + void set_ignite_status() throws InterruptedException, TimeoutException { + get_ignite_status(); + apogee_status_label.setText(String.format("\"%s\"", ignite.status_string(apogee_status))); + main_status_label.setText(String.format("\"%s\"", ignite.status_string(main_status))); + } + + void close() { + timer.stop(); + setVisible(false); + ignite.close(); + } + + void abort() { + close(); + JOptionPane.showMessageDialog(owner, + String.format("Connection to \"%s\" failed", + device.toShortString()), + "Connection Failed", + JOptionPane.ERROR_MESSAGE); + } + + void tick_timer() { + if (timer_running) { + --time_remaining; + if (time_remaining <= 0) + cancel(); + else + set_arm_text(); + } + try { + set_ignite_status(); + } catch (InterruptedException ie) { + abort(); + } catch (TimeoutException te) { + abort(); + } + } + + void fire() { + if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) { + int igniter = AltosIgnite.None; + if (apogee.isSelected() && !main.isSelected()) + igniter = AltosIgnite.Apogee; + else if (main.isSelected() && !apogee.isSelected()) + igniter = AltosIgnite.Main; + ignite.fire(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() { + device = AltosDeviceDialog.show(owner, AltosDevice.product_any); + if (device != null) { + try { + ignite = new AltosIgnite(device); + return true; + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(owner, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "Cannot open target device", + JOptionPane.ERROR_MESSAGE); + } catch (AltosSerialInUseException si) { + JOptionPane.showMessageDialog(owner, + String.format("Device \"%s\" already in use", + device.toShortString()), + "Device in use", + JOptionPane.ERROR_MESSAGE); + } catch (IOException ee) { + JOptionPane.showMessageDialog(owner, + device.toShortString(), + ee.getLocalizedMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + 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 = 1; + c.weighty = 1; + + 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); + + try { + set_ignite_status(); + } catch (InterruptedException ie) { + abort(); + return; + } catch (TimeoutException te) { + abort(); + return; + } + + 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"); + + pack(); + setLocationRelativeTo(owner); + setVisible(true); + + 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..723f8301 --- /dev/null +++ b/altosui/AltosInfoTable.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.LinkedBlockingQueue; + +public class AltosInfoTable extends JTable { + private AltosFlightInfoTableModel model; + + private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 14); + private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 14); + + static final int info_columns = 3; + static final int info_rows = 17; + + int desired_row_height() { + FontMetrics infoValueMetrics = getFontMetrics(infoValueFont); + return (infoValueMetrics.getHeight() + infoValueMetrics.getLeading()) * 18 / 10; + } + + public AltosInfoTable() { + super(new AltosFlightInfoTableModel(info_rows, info_columns)); + model = (AltosFlightInfoTableModel) getModel(); + setFont(infoValueFont); + setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS); + setShowGrid(true); + setRowHeight(desired_row_height()); + 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, "Rocket state", "%s", state.data.state()); + info_add_row(0, "Callsign", "%s", state.data.callsign); + info_add_row(0, "Rocket serial", "%6d", state.data.serial); + info_add_row(0, "Rocket flight", "%6d", state.data.flight); + + info_add_row(0, "RSSI", "%6d dBm", state.data.rssi); + info_add_row(0, "CRC Errors", "%6d", crc_errors); + 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); + info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense); + info_add_row(0, "Main", "%9.2f V", state.main_sense); + info_add_row(0, "Pad altitude", "%6.0f m", state.ground_altitude); + if (state.gps == null) { + 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..d586033f --- /dev/null +++ b/altosui/AltosKML.java @@ -0,0 +1,169 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; + +public class 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 = + " %12.7f, %12.7f, %12.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 (!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..d34efe6d --- /dev/null +++ b/altosui/AltosLanded.java @@ -0,0 +1,212 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class AltosLanded extends JComponent implements AltosFlightDisplay { + GridBagLayout layout; + Font label_font; + Font value_font; + + public class LandedValue { + JLabel label; + JTextField value; + void show(AltosState state, int crc_errors) {} + + void reset() { + value.setText(""); + } + + void show(String format, double v) { + 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(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(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) { + 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 LandedValue { + void show (AltosState state, int crc_errors) { + if (state.gps != null) + value.setText(pos(state.gps.lon,"E", "W")); + else + value.setText("???"); + } + public Lon (GridBagLayout layout, int y) { + super (layout, y, "Longitude"); + } + } + + Lon lon; + + class Bearing extends LandedValue { + void show (AltosState state, int crc_errors) { + 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) { + 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 show(AltosState state, int crc_errors) { + bearing.show(state, crc_errors); + distance.show(state, crc_errors); + lat.show(state, crc_errors); + lon.show(state, crc_errors); + height.show(state, crc_errors); + speed.show(state, crc_errors); + accel.show(state, crc_errors); + } + + public AltosLanded() { + layout = new GridBagLayout(); + + label_font = new Font("Dialog", Font.PLAIN, 22); + value_font = new Font("Monospaced", Font.PLAIN, 22); + 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); + } +} diff --git a/altosui/AltosLed.java b/altosui/AltosLed.java new file mode 100644 index 00000000..e08e9960 --- /dev/null +++ b/altosui/AltosLed.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class 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..2fa38412 --- /dev/null +++ b/altosui/AltosLights.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class 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/AltosLine.java b/altosui/AltosLine.java new file mode 100644 index 00000000..86e9d4c6 --- /dev/null +++ b/altosui/AltosLine.java @@ -0,0 +1,30 @@ +/* + * 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 AltosLine { + public String line; + + public AltosLine() { + line = null; + } + + public AltosLine(String s) { + line = s; + } +}
\ No newline at end of file diff --git a/altosui/AltosLog.java b/altosui/AltosLog.java new file mode 100644 index 00000000..dd147d21 --- /dev/null +++ b/altosui/AltosLog.java @@ -0,0 +1,115 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.io.*; +import java.lang.*; +import java.util.*; +import java.text.ParseException; +import java.util.concurrent.LinkedBlockingQueue; + +/* + * This creates a thread to capture telemetry data and write it to + * a log file + */ +class AltosLog implements Runnable { + + LinkedBlockingQueue<AltosLine> input_queue; + LinkedBlockingQueue<String> pending_queue; + int serial; + int flight; + FileWriter log_file; + Thread log_thread; + + private void close_log_file() { + if (log_file != null) { + try { + log_file.close(); + } catch (IOException io) { + } + log_file = null; + } + } + + void close() { + close_log_file(); + if (log_thread != null) { + log_thread.interrupt(); + log_thread = null; + } + } + + boolean open (AltosTelemetry telem) throws IOException { + AltosFile a = new AltosFile(telem); + + log_file = new FileWriter(a, true); + if (log_file != null) { + while (!pending_queue.isEmpty()) { + try { + String s = pending_queue.take(); + log_file.write(s); + log_file.write('\n'); + } catch (InterruptedException ie) { + } + } + log_file.flush(); + } + return log_file != null; + } + + public void run () { + try { + for (;;) { + AltosLine line = input_queue.take(); + if (line.line == null) + continue; + try { + AltosTelemetry telem = new AltosTelemetry(line.line); + if (telem.serial != serial || telem.flight != flight || log_file == null) { + close_log_file(); + serial = telem.serial; + flight = telem.flight; + open(telem); + } + } catch (ParseException pe) { + } catch (AltosCRCException ce) { + } + if (log_file != null) { + log_file.write(line.line); + log_file.write('\n'); + log_file.flush(); + } else + pending_queue.put(line.line); + } + } catch (InterruptedException ie) { + } catch (IOException ie) { + } + close(); + } + + public AltosLog (AltosSerial s) { + pending_queue = new LinkedBlockingQueue<String> (); + input_queue = new LinkedBlockingQueue<AltosLine> (); + s.add_monitor(input_queue); + serial = -1; + flight = -1; + log_file = null; + log_thread = new Thread(this); + log_thread.start(); + } +} diff --git a/altosui/AltosPad.java b/altosui/AltosPad.java new file mode 100644 index 00000000..66954347 --- /dev/null +++ b/altosui/AltosPad.java @@ -0,0 +1,269 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public class 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 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 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) { + 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) { + 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 GPSLocked extends LaunchStatus { + void show (AltosState state, int crc_errors) { + value.setText(String.format("%4d sats", state.gps.nsat)); + lights.set(state.gps.locked); + } + 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) { + 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) { + 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) { + 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(); + gps_locked.reset(); + gps_ready.reset(); + pad_lat.reset(); + pad_lon.reset(); + pad_alt.reset(); + } + + public void show(AltosState state, int crc_errors) { + battery.show(state, crc_errors); + apogee.show(state, crc_errors); + main.show(state, crc_errors); + gps_locked.show(state, crc_errors); + gps_ready.show(state, crc_errors); + pad_lat.show(state, crc_errors); + pad_lon.show(state, crc_errors); + pad_alt.show(state, crc_errors); + } + + 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); + gps_locked = new GPSLocked(layout, 3); + gps_ready = new GPSReady(layout, 4); + pad_lat = new PadLat(layout, 5); + pad_lon = new PadLon(layout, 6); + pad_alt = new PadAlt(layout, 7); + } +} diff --git a/altosui/AltosParse.java b/altosui/AltosParse.java new file mode 100644 index 00000000..fbfcaaee --- /dev/null +++ b/altosui/AltosParse.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.text.*; +import java.lang.*; + +public class AltosParse { + static boolean isdigit(char c) { + return '0' <= c && c <= '9'; + } + + static int parse_int(String v) throws ParseException { + try { + return Altos.fromdec(v); + } catch (NumberFormatException e) { + throw new ParseException("error parsing int " + v, 0); + } + } + + static int parse_hex(String v) throws ParseException { + try { + return Altos.fromhex(v); + } catch (NumberFormatException e) { + throw new ParseException("error parsing hex " + v, 0); + } + } + + static double parse_double(String v) throws ParseException { + try { + return Double.parseDouble(v); + } catch (NumberFormatException e) { + throw new ParseException("error parsing double " + v, 0); + } + } + + static double parse_coord(String coord) throws ParseException { + String[] dsf = coord.split("\\D+"); + + if (dsf.length != 3) { + throw new ParseException("error parsing coord " + coord, 0); + } + int deg = parse_int(dsf[0]); + int min = parse_int(dsf[1]); + int frac = parse_int(dsf[2]); + + double r = deg + (min + frac / 10000.0) / 60.0; + if (coord.endsWith("S") || coord.endsWith("W")) + r = -r; + return r; + } + + static String strip_suffix(String v, String suffix) { + if (v.endsWith(suffix)) + return v.substring(0, v.length() - suffix.length()); + return v; + } + + static void word(String v, String m) throws ParseException { + if (!v.equals(m)) { + throw new ParseException("error matching '" + v + "' '" + m + "'", 0); + } + } +} diff --git a/altosui/AltosPreferences.java b/altosui/AltosPreferences.java new file mode 100644 index 00000000..e2a3df3b --- /dev/null +++ b/altosui/AltosPreferences.java @@ -0,0 +1,205 @@ +/* + * 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.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.awt.Component; +import javax.swing.*; +import javax.swing.filechooser.FileSystemView; + +class AltosPreferences { + static Preferences preferences; + + /* logdir preference name */ + final static String logdirPreference = "LOGDIR"; + + /* channel preference name */ + final static String channelPreferenceFormat = "CHANNEL-%d"; + + /* voice preference name */ + final static String voicePreference = "VOICE"; + + /* callsign preference name */ + final static String callsignPreference = "CALLSIGN"; + + /* firmware directory preference name */ + final static String firmwaredirPreference = "FIRMWARE"; + + /* Default logdir is ~/TeleMetrum */ + final static String logdirName = "TeleMetrum"; + + /* UI Component to pop dialogs up */ + static Component component; + + /* Log directory */ + static File logdir; + + /* Channel (map serial to channel) */ + static Hashtable<Integer, Integer> channels; + + /* Voice preference */ + static boolean voice; + + /* Callsign preference */ + static String callsign; + + /* Firmware directory */ + static File firmwaredir; + + public static void init(Component ui) { + preferences = Preferences.userRoot().node("/org/altusmetrum/altosui"); + + component = ui; + + /* Initialize logdir from preferences */ + String logdir_string = preferences.get(logdirPreference, null); + if (logdir_string != null) + logdir = new File(logdir_string); + else { + /* Use the file system view default directory */ + logdir = new File(FileSystemView.getFileSystemView().getDefaultDirectory(), logdirName); + if (!logdir.exists()) + logdir.mkdirs(); + } + + channels = new Hashtable<Integer,Integer>(); + + voice = preferences.getBoolean(voicePreference, true); + + callsign = preferences.get(callsignPreference,"N0CALL"); + + String firmwaredir_string = preferences.get(firmwaredirPreference, null); + if (firmwaredir_string != null) + firmwaredir = new File(firmwaredir_string); + else + firmwaredir = null; + } + + static void flush_preferences() { + try { + preferences.flush(); + } catch (BackingStoreException ee) { + JOptionPane.showMessageDialog(component, + preferences.absolutePath(), + "Cannot save prefernces", + JOptionPane.ERROR_MESSAGE); + } + } + + public static void set_logdir(File new_logdir) { + logdir = new_logdir; + synchronized (preferences) { + preferences.put(logdirPreference, logdir.getPath()); + flush_preferences(); + } + } + + 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 File logdir() { + return logdir; + } + + public static void set_channel(int serial, int new_channel) { + channels.put(serial, new_channel); + synchronized (preferences) { + preferences.putInt(String.format(channelPreferenceFormat, serial), new_channel); + flush_preferences(); + } + } + + public static int channel(int serial) { + if (channels.containsKey(serial)) + return channels.get(serial); + int channel = preferences.getInt(String.format(channelPreferenceFormat, serial), 0); + channels.put(serial, channel); + return channel; + } + + public static void set_voice(boolean new_voice) { + voice = new_voice; + synchronized (preferences) { + preferences.putBoolean(voicePreference, voice); + flush_preferences(); + } + } + + public static boolean voice() { + return voice; + } + + public static void set_callsign(String new_callsign) { + callsign = new_callsign; + synchronized(preferences) { + preferences.put(callsignPreference, callsign); + flush_preferences(); + } + } + + public static String callsign() { + return callsign; + } + + public static void set_firmwaredir(File new_firmwaredir) { + firmwaredir = new_firmwaredir; + synchronized (preferences) { + preferences.put(firmwaredirPreference, firmwaredir.getPath()); + flush_preferences(); + } + } + + public static File firmwaredir() { + return firmwaredir; + } +} diff --git a/altosui/AltosReader.java b/altosui/AltosReader.java new file mode 100644 index 00000000..b9280a0c --- /dev/null +++ b/altosui/AltosReader.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 java.io.*; +import java.util.*; +import java.text.*; + +public class AltosReader { + public AltosRecord read() throws IOException, ParseException { return null; } + public void close() { } + public void write_comments(PrintStream out) { } +} diff --git a/altosui/AltosRecord.java b/altosui/AltosRecord.java new file mode 100644 index 00000000..1160a273 --- /dev/null +++ b/altosui/AltosRecord.java @@ -0,0 +1,219 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; +import java.io.*; + +public class AltosRecord { + int version; + String callsign; + int serial; + int flight; + int rssi; + int status; + int state; + int tick; + int accel; + int pres; + int temp; + int batt; + int drogue; + int main; + int flight_accel; + int ground_accel; + int flight_vel; + int flight_pres; + int ground_pres; + int accel_plus_g; + int accel_minus_g; + AltosGPS gps; + + double time; /* seconds since boost */ + + /* + * Values for our MP3H6115A pressure sensor + * + * From the data sheet: + * + * Pressure range: 15-115 kPa + * Voltage at 115kPa: 2.82 + * Output scale: 27mV/kPa + * + * + * 27 mV/kPa * 2047 / 3300 counts/mV = 16.75 counts/kPa + * 2.82V * 2047 / 3.3 counts/V = 1749 counts/115 kPa + */ + + static final double counts_per_kPa = 27 * 2047 / 3300; + static final double counts_at_101_3kPa = 1674.0; + + static double + barometer_to_pressure(double count) + { + return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0; + } + + public double raw_pressure() { + return barometer_to_pressure(pres); + } + + public double filtered_pressure() { + return barometer_to_pressure(flight_pres); + } + + public double ground_pressure() { + return barometer_to_pressure(ground_pres); + } + + public double filtered_altitude() { + return AltosConvert.pressure_to_altitude(filtered_pressure()); + } + + public double raw_altitude() { + return AltosConvert.pressure_to_altitude(raw_pressure()); + } + + public double ground_altitude() { + return AltosConvert.pressure_to_altitude(ground_pressure()); + } + + public double filtered_height() { + return filtered_altitude() - ground_altitude(); + } + + public double raw_height() { + return raw_altitude() - ground_altitude(); + } + + public double battery_voltage() { + return AltosConvert.cc_battery_to_voltage(batt); + } + + public double main_voltage() { + return AltosConvert.cc_ignitor_to_voltage(main); + } + + public double drogue_voltage() { + return AltosConvert.cc_ignitor_to_voltage(drogue); + } + + /* Value for the CC1111 built-in temperature sensor + * Output voltage at 0°C = 0.755V + * Coefficient = 0.00247V/°C + * Reference voltage = 1.25V + * + * temp = ((value / 32767) * 1.25 - 0.755) / 0.00247 + * = (value - 19791.268) / 32768 * 1.25 / 0.00247 + */ + + static double + thermometer_to_temperature(double thermo) + { + return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247; + } + + public double temperature() { + return thermometer_to_temperature(temp); + } + + double accel_counts_per_mss() { + double counts_per_g = Math.abs(accel_minus_g - accel_plus_g) / 2; + + return counts_per_g / 9.80665; + } + public double acceleration() { + return (ground_accel - accel) / accel_counts_per_mss(); + } + + public double accel_speed() { + double speed = flight_vel / (accel_counts_per_mss() * 100.0); + return speed; + } + + public String state() { + return Altos.state_name(state); + } + + public static String gets(FileInputStream s) throws IOException { + int c; + String line = ""; + + while ((c = s.read()) != -1) { + if (c == '\r') + continue; + if (c == '\n') { + return line; + } + line = line + (char) c; + } + return null; + } + + public AltosRecord(AltosRecord old) { + version = old.version; + callsign = old.callsign; + serial = old.serial; + flight = old.flight; + rssi = old.rssi; + status = old.status; + state = old.state; + tick = old.tick; + accel = old.accel; + pres = old.pres; + temp = old.temp; + batt = old.batt; + drogue = old.drogue; + main = old.main; + flight_accel = old.flight_accel; + ground_accel = old.ground_accel; + flight_vel = old.flight_vel; + flight_pres = old.flight_pres; + ground_pres = old.ground_pres; + accel_plus_g = old.accel_plus_g; + accel_minus_g = old.accel_minus_g; + gps = new AltosGPS(old.gps); + } + + public AltosRecord() { + version = 0; + callsign = "N0CALL"; + serial = 0; + flight = 0; + rssi = 0; + status = 0; + state = Altos.ao_flight_startup; + tick = 0; + accel = 0; + pres = 0; + temp = 0; + batt = 0; + drogue = 0; + main = 0; + flight_accel = 0; + ground_accel = 0; + flight_vel = 0; + flight_pres = 0; + ground_pres = 0; + accel_plus_g = 0; + accel_minus_g = 0; + gps = new AltosGPS(); + } +} diff --git a/altosui/AltosRecordIterable.java b/altosui/AltosRecordIterable.java new file mode 100644 index 00000000..a7df92d1 --- /dev/null +++ b/altosui/AltosRecordIterable.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.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +public abstract class AltosRecordIterable implements Iterable<AltosRecord> { + public abstract Iterator<AltosRecord> iterator(); + public void write_comments(PrintStream out) { } +} diff --git a/altosui/AltosReplayReader.java b/altosui/AltosReplayReader.java new file mode 100644 index 00000000..4e5e1d93 --- /dev/null +++ b/altosui/AltosReplayReader.java @@ -0,0 +1,57 @@ +/* + * 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; + +/* + * Open an existing telemetry file and replay it in realtime + */ + +public class AltosReplayReader extends AltosFlightReader { + Iterator<AltosRecord> iterator; + + public AltosRecord read() { + if (iterator.hasNext()) + return iterator.next(); + return null; + } + + public void close (boolean interrupted) { + } + + void update(AltosState state) throws InterruptedException { + /* Make it run in realtime after the rocket leaves the pad */ + if (state.state > Altos.ao_flight_pad) + Thread.sleep((int) (Math.min(state.time_change,10) * 1000)); + } + + public AltosReplayReader(Iterator<AltosRecord> in_iterator, String in_name) { + iterator = in_iterator; + name = in_name; + } +} diff --git a/altosui/AltosRomconfig.java b/altosui/AltosRomconfig.java new file mode 100644 index 00000000..55056b5e --- /dev/null +++ b/altosui/AltosRomconfig.java @@ -0,0 +1,147 @@ +/* + * 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.*; + +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..e1dc974e --- /dev/null +++ b/altosui/AltosRomconfigUI.java @@ -0,0 +1,186 @@ +/* + * 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.*; + +public class AltosRomconfigUI + extends JDialog + 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); + } + + 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; + } +} diff --git a/altosui/AltosSerial.java b/altosui/AltosSerial.java new file mode 100644 index 00000000..b19143e5 --- /dev/null +++ b/altosui/AltosSerial.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. + */ + +/* + * Deal with TeleDongle on a serial port + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; + +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 implements Runnable { + + static List<String> devices_opened = Collections.synchronizedList(new LinkedList<String>()); + + AltosDevice device; + SWIGTYPE_p_altos_file altos; + LinkedList<LinkedBlockingQueue<AltosLine>> monitors; + LinkedBlockingQueue<AltosLine> reply_queue; + Thread input_thread; + String line; + byte[] line_bytes; + int line_count; + boolean monitor_mode; + + public void run () { + int c; + + try { + for (;;) { + c = libaltos.altos_getchar(altos, 0); + if (Thread.interrupted()) + break; + if (c == libaltosConstants.LIBALTOS_ERROR) { + for (int e = 0; e < monitors.size(); e++) { + LinkedBlockingQueue<AltosLine> q = monitors.get(e); + q.put(new AltosLine()); + } + reply_queue.put (new AltosLine()); + break; + } + if (c == libaltosConstants.LIBALTOS_TIMEOUT) + continue; + if (c == '\r') + continue; + synchronized(this) { + if (c == '\n') { + if (line_count != 0) { + try { + line = new String(line_bytes, 0, line_count, "UTF-8"); + } catch (UnsupportedEncodingException ue) { + line = ""; + for (int i = 0; i < line_count; i++) + line = line + line_bytes[i]; + } + if (line.startsWith("VERSION") || line.startsWith("CRC")) { + for (int e = 0; e < monitors.size(); e++) { + LinkedBlockingQueue<AltosLine> q = monitors.get(e); + q.put(new AltosLine (line)); + } + } else { +// System.out.printf("GOT: %s\n", line); + reply_queue.put(new AltosLine (line)); + } + line_count = 0; + line = ""; + } + } else { + if (line_bytes == null) { + line_bytes = new byte[256]; + } else if (line_count == line_bytes.length) { + byte[] new_line_bytes = new byte[line_count * 2]; + System.arraycopy(line_bytes, 0, new_line_bytes, 0, line_count); + line_bytes = new_line_bytes; + } + line_bytes[line_count] = (byte) c; + line_count++; + } + } + } + } catch (InterruptedException e) { + } + } + + public void flush_output() { + if (altos != null) + libaltos.altos_flush(altos); + } + + public void flush_input() { + flush_output(); + boolean got_some; + do { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + } + got_some = !reply_queue.isEmpty(); + synchronized(this) { + if (!"VERSION".startsWith(line) && + !line.startsWith("VERSION")) + line = ""; + reply_queue.clear(); + } + } while (got_some); + } + + public String get_reply() throws InterruptedException { + flush_output(); + AltosLine line = reply_queue.take(); + return line.line; + } + + public String get_reply(int timeout) throws InterruptedException { + flush_output(); + AltosLine line = reply_queue.poll(timeout, TimeUnit.MILLISECONDS); + if (line == null) + return null; + return line.line; + } + + public void add_monitor(LinkedBlockingQueue<AltosLine> q) { + set_monitor(true); + monitors.add(q); + } + + public void remove_monitor(LinkedBlockingQueue<AltosLine> q) { + monitors.remove(q); + if (monitors.isEmpty()) + set_monitor(false); + } + + public void close() { + 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()); + } + } + + public void putc(char c) { + if (altos != null) + libaltos.altos_putchar(altos, c); + } + + public void print(String data) { +// System.out.printf("\"%s\" ", data); + for (int i = 0; i < data.length(); i++) + putc(data.charAt(i)); + } + + public void printf(String format, Object ... arguments) { + print(String.format(format, arguments)); + } + + 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 = libaltos.altos_open(device); + if (altos == null) { + close(); + throw new FileNotFoundException(device.toShortString()); + } + input_thread = new Thread(this); + input_thread.start(); + print("~\nE 0\n"); + set_monitor(false); + flush_output(); + } + + public void set_radio() { + set_channel(AltosPreferences.channel(device.getSerial())); + set_callsign(AltosPreferences.callsign()); + } + + public void set_channel(int channel) { + if (altos != null) { + if (monitor_mode) + printf("m 0\nc r %d\nm 1\n", channel); + else + printf("c r %d\n", channel); + flush_output(); + } + } + + void set_monitor(boolean monitor) { + monitor_mode = monitor; + if (altos != null) { + if (monitor) + printf("m 1\n"); + else + printf("m 0\n"); + flush_output(); + } + } + + public void set_callsign(String callsign) { + if (altos != null) { + printf ("c c %s\n", callsign); + flush_output(); + } + } + + public AltosSerial(AltosDevice in_device) throws FileNotFoundException, AltosSerialInUseException { + device = in_device; + line = ""; + monitor_mode = false; + monitors = new LinkedList<LinkedBlockingQueue<AltosLine>> (); + reply_queue = new LinkedBlockingQueue<AltosLine> (); + open(); + } +} diff --git a/altosui/AltosSerialInUseException.java b/altosui/AltosSerialInUseException.java new file mode 100644 index 00000000..4b108c7c --- /dev/null +++ b/altosui/AltosSerialInUseException.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 libaltosJNI.*; + +public class AltosSerialInUseException extends Exception { + public altos_device device; + + public AltosSerialInUseException (altos_device in_device) { + device = in_device; + } +} diff --git a/altosui/AltosSerialMonitor.java b/altosui/AltosSerialMonitor.java new file mode 100644 index 00000000..ad0e9295 --- /dev/null +++ b/altosui/AltosSerialMonitor.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +public interface AltosSerialMonitor { + void data(String data); +} diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java new file mode 100644 index 00000000..80970605 --- /dev/null +++ b/altosui/AltosSiteMap.java @@ -0,0 +1,388 @@ +/* + * 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; + +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; + + 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); + } + + HashMap<Point,AltosSiteMapTile> mapTiles = new HashMap<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 + } + + private void bgLoadMap(final AltosSiteMapTile tile, + final File pngfile, final String pngurl) + { + //System.out.printf("Loading/fetching map %s\n", pngfile); + Thread thread = new Thread() { + public void run() { + ImageIcon res; + res = AltosSiteMapCache.fetchAndLoadMap(pngfile, pngurl); + if (res != null) { + tile.loadMap(res); + } else { + System.out.printf("# Failed to fetch file %s\n", pngfile); + System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); + } + } + }; + thread.start(); + } + + public static void prefetchMaps(double lat, double lng, int w, int h) { + AltosPreferences.init(null); + + 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++) { + LatLng map_latlng = asm.latlng( + -asm.centre.x + x*px_size + px_size/2, + -asm.centre.y + y*px_size + px_size/2); + File pngfile = asm.MapFile(map_latlng.lat, map_latlng.lng); + String pngurl = asm.MapURL(map_latlng.lat, map_latlng.lng); + if (pngfile.exists()) { + System.out.printf("Already have %s\n", pngfile); + } else if (AltosSiteMapCache.fetchMap(pngfile, pngurl)) { + System.out.printf("Fetched map %s\n", pngfile); + } else { + System.out.printf("# Failed to fetch file %s\n", pngfile); + System.out.printf(" wget -O '%s' ''\n", pngfile, pngurl); + } + } + } + } + + private void initMap(AltosSiteMapTile tile, Point 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); + String pngurl = MapURL(map_latlng.lat, map_latlng.lng); + bgLoadMap(tile, pngfile, pngurl); + } + + private void initMaps(double lat, double lng) { + centre = getBaseLocation(lat, lng); + + for (Point k : mapTiles.keySet()) { + initMap(mapTiles.get(k), k); + } + } + + private File MapFile(double lat, double lng) { + char chlat = lat < 0 ? 'S' : 'N'; + char chlng = lng < 0 ? 'E' : 'W'; + if (lat < 0) lat = -lat; + if (lng < 0) lng = -lng; + return new File(AltosPreferences.logdir(), + String.format("map-%c%.6f,%c%.6f-%d.png", + chlat, lat, chlng, lng, zoom)); + } + + private String MapURL(double lat, double lng) { + 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(final AltosState state, final int crc_errors) { + // if insufficient gps data, nothing to update + if (state.gps == null) + return; + if (state.pad_lat == 0 && state.pad_lon == 0) + return; + if (!state.gps.locked) { + if (state.gps.nsat < 4) + return; + } + + if (!initialised) { + initMaps(state.pad_lat, state.pad_lon); + initialised = true; + } + + 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(tile, offset); + finishTileLater(tile, offset); + } + + scrollRocketToVisible(pt); + + if (offset != tileOffset(last_pt)) { + ensureTilesAround(offset); + } + + last_pt = pt; + last_state = state.state; + } + + 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 = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + Point offset = new Point(base_offset.x + x, base_offset.y + y); + if (mapTiles.containsKey(offset)) + continue; + AltosSiteMapTile tile = createTile(offset); + initMap(tile, 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() { + GrabNDrag scroller = new GrabNDrag(comp); + + comp.setLayout(layout); + + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + Point offset = new Point(x, y); + AltosSiteMapTile t = createTile(offset); + addTileAt(t, offset); + } + } + setViewportView(comp); + setPreferredSize(new Dimension(500,200)); + } +} diff --git a/altosui/AltosSiteMapCache.java b/altosui/AltosSiteMapCache.java new file mode 100644 index 00000000..2e62cc45 --- /dev/null +++ b/altosui/AltosSiteMapCache.java @@ -0,0 +1,103 @@ +/* + * 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; + +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/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java new file mode 100644 index 00000000..8301f42b --- /dev/null +++ b/altosui/AltosSiteMapTile.java @@ -0,0 +1,112 @@ +/* + * 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; + +public class AltosSiteMapTile extends JLayeredPane { + JLabel mapLabel; + JLabel draw; + Graphics2D g2d; + + public void loadMap(ImageIcon icn) { + mapLabel.setIcon(icn); + } + + 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 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 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/AltosState.java b/altosui/AltosState.java new file mode 100644 index 00000000..ec499d5a --- /dev/null +++ b/altosui/AltosState.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. + */ + +/* + * Track flight state from telemetry or eeprom data stream + */ + +package altosui; + +public class AltosState { + AltosRecord data; + + /* derived data */ + + long report_time; + + double time_change; + int tick; + + int state; + boolean landed; + boolean ascent; /* going up? */ + + double ground_altitude; + double height; + double speed; + double acceleration; + double battery; + double temperature; + double main_sense; + double drogue_sense; + double baro_speed; + + double max_height; + double max_acceleration; + double max_speed; + + AltosGPS gps; + + double pad_lat; + double pad_lon; + double pad_alt; + + static final int MIN_PAD_SAMPLES = 10; + + int npad; + int ngps; + int gps_waiting; + boolean gps_ready; + + AltosGreatCircle from_pad; + double elevation; /* from pad */ + double range; /* total distance */ + + double gps_height; + + int speak_tick; + double speak_altitude; + + + void init (AltosRecord cur, AltosState prev_state) { + int i; + AltosRecord prev; + + data = cur; + + ground_altitude = data.ground_altitude(); + height = data.filtered_altitude() - ground_altitude; + + report_time = System.currentTimeMillis(); + + acceleration = data.acceleration(); + speed = data.accel_speed(); + temperature = data.temperature(); + drogue_sense = data.drogue_voltage(); + main_sense = data.main_voltage(); + battery = data.battery_voltage(); + tick = data.tick; + state = data.state; + + if (prev_state != null) { + + /* Preserve any existing gps data */ + npad = prev_state.npad; + ngps = prev_state.ngps; + gps = prev_state.gps; + pad_lat = prev_state.pad_lat; + pad_lon = prev_state.pad_lon; + pad_alt = prev_state.pad_alt; + max_height = prev_state.max_height; + max_acceleration = prev_state.max_acceleration; + max_speed = prev_state.max_speed; + + /* make sure the clock is monotonic */ + while (tick < prev_state.tick) + tick += 65536; + + time_change = (tick - prev_state.tick) / 100.0; + + /* compute barometric speed */ + + double height_change = height - prev_state.height; + if (time_change > 0) + baro_speed = (prev_state.baro_speed * 3 + (height_change / time_change)) / 4.0; + else + baro_speed = prev_state.baro_speed; + } else { + npad = 0; + ngps = 0; + gps = null; + baro_speed = 0; + time_change = 0; + } + + if (state == Altos.ao_flight_pad) { + + /* Track consecutive 'good' gps reports, waiting for 10 of them */ + if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) + npad++; + else + npad = 0; + + /* Average GPS data while on the pad */ + if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) { + if (ngps > 1) { + /* filter pad position */ + pad_lat = (pad_lat * 31.0 + data.gps.lat) / 32.0; + pad_lon = (pad_lon * 31.0 + data.gps.lon) / 32.0; + pad_alt = (pad_alt * 31.0 + data.gps.alt) / 32.0; + } else { + pad_lat = data.gps.lat; + pad_lon = data.gps.lon; + pad_alt = data.gps.alt; + } + ngps++; + } + } + + gps_waiting = MIN_PAD_SAMPLES - npad; + if (gps_waiting < 0) + gps_waiting = 0; + + gps_ready = gps_waiting == 0; + + ascent = (Altos.ao_flight_boost <= state && + state <= Altos.ao_flight_coast); + + /* Only look at accelerometer data on the way up */ + if (ascent && acceleration > max_acceleration) + max_acceleration = acceleration; + if (ascent && speed > max_speed) + max_speed = speed; + + if (height > max_height) + max_height = height; + if (data.gps != null) { + if (gps == null || !gps.locked || data.gps.locked) + gps = data.gps; + if (ngps > 0 && gps.locked) { + from_pad = new AltosGreatCircle(pad_lat, pad_lon, gps.lat, gps.lon); + } + } + elevation = 0; + range = -1; + if (ngps > 0) { + gps_height = gps.alt - pad_alt; + if (from_pad != null) { + elevation = Math.atan2(height, from_pad.distance) * 180 / Math.PI; + range = Math.sqrt(height * height + from_pad.distance * from_pad.distance); + } + } else { + gps_height = 0; + } + } + + public AltosState(AltosRecord cur) { + init(cur, null); + } + + public AltosState (AltosRecord cur, AltosState prev) { + init(cur, prev); + } +} diff --git a/altosui/AltosTelemetry.java b/altosui/AltosTelemetry.java new file mode 100644 index 00000000..bdb6466a --- /dev/null +++ b/altosui/AltosTelemetry.java @@ -0,0 +1,143 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.text.*; +import java.util.HashMap; + +/* + * Telemetry data contents + */ + + +/* + * The telemetry data stream is a bit of a mess at present, with no consistent + * formatting. In particular, the GPS data is formatted for viewing instead of parsing. + * However, the key feature is that every telemetry line contains all of the information + * necessary to describe the current rocket state, including the calibration values + * for accelerometer and barometer. + * + * GPS unlocked: + * + * VERSION 2 CALL KB0G SERIAL 51 FLIGHT 2 RSSI -68 STATUS ff STATE pad 1001 \ + * a: 16032 p: 21232 t: 20284 v: 25160 d: 204 m: 204 fa: 16038 ga: 16032 fv: 0 \ + * fp: 21232 gp: 21230 a+: 16049 a-: 16304 GPS 0 sat unlocked SAT 1 15 30 + * + * GPS locked: + * + * VERSION 2 CALL KB0G SERIAL 51 FLIGHT 2 RSSI -71 STATUS ff STATE pad 2504 \ + * a: 16028 p: 21220 t: 20360 v: 25004 d: 208 m: 200 fa: 16031 ga: 16032 fv: 330 \ + * fp: 21231 gp: 21230 a+: 16049 a-: 16304 \ + * GPS 9 sat 2010-02-13 17:16:51 35°20.0803'N 106°45.2235'W 1790m \ + * 0.00m/s(H) 0° 0.00m/s(V) 1.0(hdop) 0(herr) 0(verr) \ + * SAT 10 29 30 24 28 5 25 21 20 15 33 1 23 30 24 18 26 10 29 2 26 + */ + +public class AltosTelemetry extends AltosRecord { + public AltosTelemetry(String line) throws ParseException, AltosCRCException { + String[] words = line.split("\\s+"); + int i = 0; + + if (words[i].equals("CRC") && words[i+1].equals("INVALID")) { + i += 2; + AltosParse.word(words[i++], "RSSI"); + rssi = AltosParse.parse_int(words[i++]); + throw new AltosCRCException(rssi); + } + if (words[i].equals("CALL")) { + version = 0; + } else { + AltosParse.word (words[i++], "VERSION"); + version = AltosParse.parse_int(words[i++]); + } + + AltosParse.word (words[i++], "CALL"); + callsign = words[i++]; + + AltosParse.word (words[i++], "SERIAL"); + serial = AltosParse.parse_int(words[i++]); + + if (version >= 2) { + AltosParse.word (words[i++], "FLIGHT"); + flight = AltosParse.parse_int(words[i++]); + } else + flight = 0; + + AltosParse.word(words[i++], "RSSI"); + rssi = AltosParse.parse_int(words[i++]); + + /* Older telemetry data had mis-computed RSSI value */ + if (version <= 2) + rssi = (rssi + 74) / 2 - 74; + + AltosParse.word(words[i++], "STATUS"); + status = AltosParse.parse_hex(words[i++]); + + AltosParse.word(words[i++], "STATE"); + state = Altos.state(words[i++]); + + tick = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "a:"); + accel = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "p:"); + pres = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "t:"); + temp = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "v:"); + batt = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "d:"); + drogue = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "m:"); + main = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "fa:"); + flight_accel = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "ga:"); + ground_accel = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "fv:"); + flight_vel = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "fp:"); + flight_pres = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "gp:"); + ground_pres = AltosParse.parse_int(words[i++]); + + if (version >= 1) { + AltosParse.word(words[i++], "a+:"); + accel_plus_g = AltosParse.parse_int(words[i++]); + + AltosParse.word(words[i++], "a-:"); + accel_minus_g = AltosParse.parse_int(words[i++]); + } else { + accel_plus_g = ground_accel; + accel_minus_g = ground_accel + 530; + } + + gps = new AltosGPS(words, i, version); + } +} diff --git a/altosui/AltosTelemetryIterable.java b/altosui/AltosTelemetryIterable.java new file mode 100644 index 00000000..a71ab872 --- /dev/null +++ b/altosui/AltosTelemetryIterable.java @@ -0,0 +1,82 @@ +/* + * 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.*; +import java.text.*; + +public class AltosTelemetryIterable extends AltosRecordIterable { + LinkedList<AltosRecord> records; + + public Iterator<AltosRecord> iterator () { + return records.iterator(); + } + + public AltosTelemetryIterable (FileInputStream input) { + boolean saw_boost = false; + int current_tick = 0; + int boost_tick = 0; + + records = new LinkedList<AltosRecord> (); + + try { + for (;;) { + String line = AltosRecord.gets(input); + if (line == null) { + break; + } + try { + AltosTelemetry record = new AltosTelemetry(line); + if (record == null) + break; + if (records.isEmpty()) { + current_tick = record.tick; + } else { + int tick = record.tick | (current_tick & ~ 0xffff); + if (tick < current_tick - 0x1000) + tick += 0x10000; + current_tick = tick; + record.tick = current_tick; + } + if (!saw_boost && record.state >= Altos.ao_flight_boost) + { + saw_boost = true; + boost_tick = record.tick; + } + records.add(record); + } catch (ParseException pe) { + System.out.printf("parse exception %s\n", pe.getMessage()); + } catch (AltosCRCException ce) { + System.out.printf("crc error\n"); + } + } + } catch (IOException io) { + System.out.printf("io exception\n"); + } + + /* adjust all tick counts to be relative to boost time */ + for (AltosRecord r : this) + r.time = (r.tick - boost_tick) / 100.0; + + try { + input.close(); + } catch (IOException ie) { + } + } +} diff --git a/altosui/AltosTelemetryReader.java b/altosui/AltosTelemetryReader.java new file mode 100644 index 00000000..6c5a9397 --- /dev/null +++ b/altosui/AltosTelemetryReader.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.text.*; +import java.io.*; +import java.util.concurrent.*; + +class AltosTelemetryReader extends AltosFlightReader { + AltosDevice device; + AltosSerial serial; + AltosLog log; + + LinkedBlockingQueue<AltosLine> telem; + + AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { + AltosLine l = telem.take(); + if (l.line == null) + throw new IOException("IO error"); + return new AltosTelemetry(l.line); + } + + void close(boolean interrupted) { + serial.remove_monitor(telem); + log.close(); + serial.close(); + } + + void set_channel(int channel) { + serial.set_channel(channel); + AltosPreferences.set_channel(device.getSerial(), channel); + } + + public AltosTelemetryReader (AltosDevice in_device) + throws FileNotFoundException, AltosSerialInUseException, IOException { + device = in_device; + serial = new AltosSerial(device); + log = new AltosLog(serial); + name = device.toShortString(); + + telem = new LinkedBlockingQueue<AltosLine>(); + serial.set_radio(); + serial.add_monitor(telem); + } +} diff --git a/altosui/AltosUI.app/Contents/Info.plist b/altosui/AltosUI.app/Contents/Info.plist new file mode 100644 index 00000000..97b1b59c --- /dev/null +++ b/altosui/AltosUI.app/Contents/Info.plist @@ -0,0 +1,38 @@ +<?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>100.0</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>CFBundleSignature</key> + <string>????</string> + <key>CFBundleGetInfoString</key> + <string>AltOS UI version 0.7</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> + </dict> +</dict> +</plist> 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..94c4dd2a --- /dev/null +++ b/altosui/AltosUI.java @@ -0,0 +1,405 @@ +/* + * 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 libaltosJNI.*; + +public class AltosUI extends JFrame { + public AltosVoice voice = new AltosVoice(); + + public static boolean load_library(Frame frame) { + if (!AltosDevice.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(device); + if (reader != null) + new AltosFlightUI(voice, reader, device.getSerial()); + } catch (FileNotFoundException ee) { + JOptionPane.showMessageDialog(AltosUI.this, + String.format("Cannot open device \"%s\"", + device.toShortString()), + "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); + } + } + + 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()); + + AltosPreferences.init(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 = addButton(1, 0, "Save Flight Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SaveFlightData(); + } + }); + b = addButton(2, 0, "Replay Flight"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Replay(); + } + }); + b = addButton(3, 0, "Graph Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + GraphData(); + } + }); + b = addButton(4, 0, "Export Data"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ExportData(); + } + }); + b = addButton(0, 1, "Configure TeleMetrum"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureTeleMetrum(); + } + }); + + b = addButton(1, 1, "Configure AltosUI"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConfigureAltosUI(); + } + }); + + b = addButton(2, 1, "Flash Image"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FlashImage(); + } + }); + + b = addButton(3, 1, "Fire Igniter"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + FireIgniter(); + } + }); + + b = addButton(4, 1, "Quit"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + }); + + 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, + AltosDevice.product_basestation); + + if (device != null) + telemetry_window(device); + } + + void ConfigureCallsign() { + String result; + result = JOptionPane.showInputDialog(AltosUI.this, + "Configure Callsign", + AltosPreferences.callsign()); + if (result != null) + AltosPreferences.set_callsign(result); + } + + void ConfigureTeleMetrum() { + new AltosConfig(AltosUI.this); + } + + void FlashImage() { + new AltosFlashUI(AltosUI.this); + } + + void FireIgniter() { + new AltosIgniteUI(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.filename()); + new AltosFlightUI(voice, reader); + } + } + + /* Connect to TeleMetrum, either directly or through + * a TeleDongle over the packet link + */ + private void SaveFlightData() { + new AltosEepromDownload(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; + new AltosGraphUI(record_reader); + } + + private void ConfigureAltosUI() { + new AltosConfigureUI(AltosUI.this, voice); + } + + 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 + return new AltosTelemetryIterable(in); + } catch (FileNotFoundException fe) { + System.out.printf("Cannot open '%s'\n", filename); + return null; + } + } + + static AltosWriter open_csv(String filename) { + File file = new File (filename); + try { + return new AltosCSV(file); + } catch (FileNotFoundException fe) { + System.out.printf("Cannot open '%s'\n", filename); + return null; + } + } + + static AltosWriter open_kml(String filename) { + File file = new File (filename); + try { + return new AltosKML(file); + } catch (FileNotFoundException fe) { + System.out.printf("Cannot open '%s'\n", filename); + return null; + } + } + + static final int process_csv = 1; + static final int process_kml = 2; + + static void process_file(String input, int process) { + AltosRecordIterable iterable = open_logfile(input); + if (iterable == null) + return; + if (process == 0) + process = process_csv; + if ((process & process_csv) != 0) { + 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) { + writer.write(iterable); + writer.close(); + } + } + } + if ((process & process_kml) != 0) { + 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(); + } + } + } + + public static void main(final String[] args) { + int process = 0; + /* Handle batch-mode */ + if (args.length == 1 && args[0].equals("--help")) { + 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(" --csv\tgenerate comma separated output for spreadsheets, etc\n"); + System.out.printf(" --kml\tgenerate KML output for use with Google Earth\n"); + } else if (args.length == 3 && args[0].equals("--fetchmaps")) { + double lat = Double.parseDouble(args[1]); + double lon = Double.parseDouble(args[2]); + AltosSiteMap.prefetchMaps(lat, lon, 5, 5); + } else if (args.length == 2 && args[0].equals("--replay")) { + String filename = args[1]; + FileInputStream in; + try { + in = new FileInputStream(filename); + } catch (Exception e) { + System.out.printf("Failed to open file '%s'\n", filename); + return; + } + AltosRecordIterable recs; + AltosReplayReader reader; + if (filename.endsWith("eeprom")) { + recs = new AltosEepromIterable(in); + } else { + recs = new AltosTelemetryIterable(in); + } + reader = new AltosReplayReader(recs.iterator(), filename); + AltosFlightUI flight_ui = new AltosFlightUI(new AltosVoice(), reader); + flight_ui.set_exit_on_close(); + return; + } else if (args.length > 0) { + for (int i = 0; i < args.length; i++) { + if (args[i].equals("--kml")) + process |= process_kml; + else if (args[i].equals("--csv")) + process |= process_csv; + else + process_file(args[i], process); + } + } else { + AltosUI altosui = new AltosUI(); + altosui.setVisible(true); + + AltosDevice[] devices = AltosDevice.list(AltosDevice.product_basestation); + for (int i = 0; i < devices.length; i++) + altosui.telemetry_window(devices[i]); + } + } +} diff --git a/altosui/AltosVoice.java b/altosui/AltosVoice.java new file mode 100644 index 00000000..ac13ee14 --- /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 (AltosPreferences.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..a172dff0 --- /dev/null +++ b/altosui/AltosWriter.java @@ -0,0 +1,32 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.lang.*; +import java.io.*; +import java.text.*; +import java.util.*; + +public 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..e6b87b58 --- /dev/null +++ b/altosui/GrabNDrag.java @@ -0,0 +1,54 @@ +/* + * 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.*; + +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/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..a95a5aa8 --- /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..e2ff55af --- /dev/null +++ b/altosui/Makefile.am @@ -0,0 +1,272 @@ +SUBDIRS=libaltos +JAVAROOT=classes +AM_JAVACFLAGS=-encoding UTF-8 + +man_MANS=altosui.1 + +altoslibdir=$(libdir)/altos + +CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:libaltos:$(FREETTS)/*:/usr/share/java/*" + +bin_SCRIPTS=altosui + +altosui_JAVA = \ + GrabNDrag.java \ + AltosAscent.java \ + AltosChannelMenu.java \ + AltosConfig.java \ + AltosConfigUI.java \ + AltosConfigureUI.java \ + AltosConvert.java \ + AltosCRCException.java \ + AltosCSV.java \ + AltosCSVUI.java \ + AltosDebug.java \ + AltosDescent.java \ + AltosDeviceDialog.java \ + AltosDevice.java \ + AltosDisplayThread.java \ + AltosEepromDownload.java \ + AltosEepromMonitor.java \ + AltosEepromIterable.java \ + AltosEepromRecord.java \ + AltosFile.java \ + AltosFlash.java \ + AltosFlashUI.java \ + AltosFlightDisplay.java \ + AltosFlightInfoTableModel.java \ + AltosFlightReader.java \ + AltosFlightStatus.java \ + AltosFlightUI.java \ + AltosGPS.java \ + AltosGreatCircle.java \ + AltosHexfile.java \ + Altos.java \ + AltosIgnite.java \ + AltosIgniteUI.java \ + AltosInfoTable.java \ + AltosKML.java \ + AltosLanded.java \ + AltosLed.java \ + AltosLights.java \ + AltosLine.java \ + AltosLog.java \ + AltosPad.java \ + AltosParse.java \ + AltosPreferences.java \ + AltosReader.java \ + AltosRecord.java \ + AltosRecordIterable.java \ + AltosTelemetryReader.java \ + AltosReplayReader.java \ + AltosRomconfig.java \ + AltosRomconfigUI.java \ + AltosSerial.java \ + AltosSerialInUseException.java \ + AltosSerialMonitor.java \ + AltosSiteMap.java \ + AltosSiteMapCache.java \ + AltosSiteMapTile.java \ + AltosState.java \ + AltosTelemetry.java \ + AltosTelemetryIterable.java \ + AltosUI.java \ + AltosWriter.java \ + AltosDataPointReader.java \ + AltosDataPoint.java \ + AltosGraph.java \ + AltosGraphTime.java \ + AltosGraphUI.java \ + AltosDataChooser.java \ + AltosVoice.java + +JFREECHART_CLASS= \ + jfreechart.jar + +JCOMMON_CLASS=\ + jcommon.jar + +FREETTS_CLASS= \ + cmudict04.jar \ + cmulex.jar \ + cmu_time_awb.jar \ + cmutimelex.jar \ + cmu_us_kal.jar \ + en_us.jar \ + freetts.jar + +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=$(top_srcdir)/src/teledongle-v0.2-$(VERSION).ihx +FIRMWARE_TM=$(top_srcdir)/src/telemetrum-v1.0-$(VERSION).ihx +FIRMWARE=$(FIRMWARE_TM) $(FIRMWARE_TD) + +# 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) $(FREETTS_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) + +LINUX_FILES=$(FAT_FILES) libaltos.so $(FIRMWARE) +LINUX_EXTRA=altosui-fat + +MACOSX_FILES=$(FAT_FILES) libaltos.dylib +MACOSX_EXTRA=$(FIRMWARE) + +WINDOWS_FILES=$(FAT_FILES) altos.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) $(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) + jar cfm $@ Manifest.txt \ + $(ICONJAR) \ + -C classes altosui \ + -C libaltos libaltosJNI + +$(FATJAR): classaltosui.stamp Manifest-fat.txt $(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: $(FREETTS)/freetts.jar $(JFREECHART)/jfreechart.jar $(JCOMMON)/jcommon.jar" >> $@ + +Manifest-fat.txt: + echo 'Main-Class: altosui.AltosUI' > $@ + echo "Class-Path: 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: + -rm -f "$@" + $(LN_S) libaltos/.libs/"$@" . + +libaltos.dylib: + -rm -f "$@" + $(LN_S) libaltos/"$@" . + +altos.dll: + -rm -f "$@" + $(LN_S) libaltos/"$@" . + +$(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/ + mkdir -p macosx/AltOS macosx/AltosUI.app/Contents/Resources/Java + cp -p $(FATJAR) macosx/AltosUI.app/Contents/Resources/Java/altosui.jar + cp -p $(FREETTS_CLASS) libaltos.dylib macosx/AltosUI.app/Contents/Resources/Java + cp -p $(JFREECHART_CLASS) libaltos.dylib 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 diff --git a/altosui/altos-windows.nsi b/altosui/altos-windows.nsi new file mode 100644 index 00000000..37777fd6 --- /dev/null +++ b/altosui/altos-windows.nsi @@ -0,0 +1,113 @@ +!addplugindir Instdrv/NSIS/Plugins + +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" + +; 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} "Altus Metrum" + 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" +SectionEnd + +Section "AltosUI Application" + 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 "*.dll" + + File "../../icon/*.ico" + + CreateShortCut "$SMPROGRAMS\AltusMetrum.lnk" "$INSTDIR\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/teledongle-v0.2/teledongle-v0.2-${VERSION}.ihx" + +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} "Altus Metrum" + 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/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..388d2104 --- /dev/null +++ b/altosui/libaltos/Makefile.am @@ -0,0 +1,41 @@ +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= + +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 + +clean-local: + -rm -rf libaltosJNI *.class *.java classlibaltos.stamp $(SWIG_FILE) libaltos_wrap.c diff --git a/altosui/libaltos/altos.dll b/altosui/libaltos/altos.dll Binary files differnew file mode 100755 index 00000000..28e9b4ad --- /dev/null +++ b/altosui/libaltos/altos.dll diff --git a/altosui/libaltos/cjnitest.c b/altosui/libaltos/cjnitest.c new file mode 100644 index 00000000..c6d6e069 --- /dev/null +++ b/altosui/libaltos/cjnitest.c @@ -0,0 +1,43 @@ +#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; + + 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); + altos_fini(); +} diff --git a/altosui/libaltos/libaltos.c b/altosui/libaltos/libaltos.c new file mode 100644 index 00000000..465f0ac8 --- /dev/null +++ b/altosui/libaltos/libaltos.c @@ -0,0 +1,1028 @@ +/* + * 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 USE_POLL + +PUBLIC int +altos_init(void) +{ + return LIBALTOS_SUCCESS; +} + +PUBLIC void +altos_fini(void) +{ +} + +#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 + +/* + * 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> + +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); +} + +#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; +} + +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; +} + +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; + } + } +} + +void +altos_list_finish(struct altos_list *list) +{ + IOObjectRelease (list->iterator); + free(list); +} + +#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; +}; + +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) + return NULL; + + file->fd = open(device->path, O_RDWR | O_NOCTTY); + if (file->fd < 0) { + perror(device->path); + 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) { + perror(device->path); + close(file->fd); + free(file); + return NULL; + } +#endif + ret = tcgetattr(file->fd, &term); + if (ret < 0) { + perror("tcgetattr"); + 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) { + perror("tcsetattr"); + 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) + return -errno; + 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 0; +} + +#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) { + perror("altos_getchar"); + 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) { + perror("altos_getchar"); + 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 */ + +#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; +}; + +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) { + printf("SetupDiGetClassDevs failed %d\n", GetLastError()); + 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; + char port[128]; + DWORD port_len; + char friendlyname[256]; + char symbolic[256]; + DWORD symbolic_len; + HKEY dev_key; + 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) { + 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) { + printf("cannot find SymbolicName value\n"); + RegCloseKey(dev_key); + continue; + } + vid = pid = serial = 0; + sscanf(symbolic + sizeof("\\??\\USB#VID_") - 1, + "%04X", &vid); + sscanf(symbolic + sizeof("\\??\\USB#VID_XXXX&PID_") - 1, + "%04X", &pid); + sscanf(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) { + 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)) + { + printf("Failed to get friendlyname\n"); + continue; + } + device->vendor = vid; + device->product = pid; + device->serial = serial; + strcpy(device->name, friendlyname); + + strcpy(device->path, port); + return 1; + } + result = GetLastError(); + if (result != ERROR_NO_MORE_ITEMS) + printf ("SetupDiEnumDeviceInfo failed error %d\n", 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) + 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)) + 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; + char *data = file->out_data; + char used = file->out_used; + DWORD ret; + + while (used) { + if (!WriteFile(file->handle, data, used, &put, &file->ov_write)) { + if (GetLastError() != ERROR_IO_PENDING) + 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)) + return LIBALTOS_ERROR; + break; + default: + 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) { + 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)) { + 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)) { + 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); +} + +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; +} + +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++]; +} + +#endif diff --git a/altosui/libaltos/libaltos.dylib b/altosui/libaltos/libaltos.dylib Binary files differnew file mode 100755 index 00000000..89aa12e7 --- /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..6e94899e --- /dev/null +++ b/altosui/libaltos/libaltos.h @@ -0,0 +1,102 @@ +/* + * 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 + +#define USB_VENDOR_FSF 0xfffe +#define USB_VENDOR_ALTUSMETRUM USB_VENDOR_FSF +#define USB_PRODUCT_ALTUSMETRUM 0x000a +#define USB_PRODUCT_TELEMETRUM 0x000b +#define USB_PRODUCT_TELEDONGLE 0x000c +#define USB_PRODUCT_TELETERRA 0x000d +#define USB_PRODUCT_ALTUSMETRUM_MIN 0x000a +#define USB_PRODUCT_ALTUSMETRUM_MAX 0x0013 + +#define USB_IS_ALTUSMETRUM(v,p) ((v) == USB_VENDOR_ALTUSMETRUM && \ + (USB_PRODUCT_ALTUSMETRUM_MIN <= (p) && \ + (p) <= USB_PRODUCT_ALTUSMETRUM_MAX)) + +struct altos_device { + //%immutable; + int vendor; + int product; + int serial; + char name[256]; + char path[256]; + //%mutable; +}; + +#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 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); + +#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" +%} + |