diff options
-rw-r--r-- | ao-tools/altosui/AltosCSV.java | 20 | ||||
-rw-r--r-- | ao-tools/altosui/AltosCSVUI.java | 15 | ||||
-rw-r--r-- | ao-tools/altosui/AltosDisplayThread.java | 255 | ||||
-rw-r--r-- | ao-tools/altosui/AltosEepromIterable.java (renamed from ao-tools/altosui/AltosEepromReader.java) | 329 | ||||
-rw-r--r-- | ao-tools/altosui/AltosLogfileChooser.java | 11 | ||||
-rw-r--r-- | ao-tools/altosui/AltosRecord.java | 2 | ||||
-rw-r--r-- | ao-tools/altosui/AltosRecordIterable.java | 42 | ||||
-rw-r--r-- | ao-tools/altosui/AltosReplayThread.java | 83 | ||||
-rw-r--r-- | ao-tools/altosui/AltosTelemetryIterable.java (renamed from ao-tools/altosui/AltosTelemetryReader.java) | 25 | ||||
-rw-r--r-- | ao-tools/altosui/AltosUI.java | 282 | ||||
-rw-r--r-- | ao-tools/altosui/Makefile.am | 7 |
11 files changed, 599 insertions, 472 deletions
diff --git a/ao-tools/altosui/AltosCSV.java b/ao-tools/altosui/AltosCSV.java index f7b3c03c..7f14adad 100644 --- a/ao-tools/altosui/AltosCSV.java +++ b/ao-tools/altosui/AltosCSV.java @@ -207,22 +207,10 @@ public class AltosCSV { out.close(); } - public void write(AltosReader reader) { - AltosRecord record; - - reader.write_comments(out()); - try { - for (;;) { - record = reader.read(); - if (record == null) - break; - write(record); - } - } catch (IOException ie) { - System.out.printf("IOException\n"); - } catch (ParseException pe) { - System.out.printf("ParseException %s\n", pe.getMessage()); - } + public void write(AltosRecordIterable iterable) { + iterable.write_comments(out()); + for (AltosRecord r : iterable) + write(r); } public AltosCSV(File in_name) throws FileNotFoundException { diff --git a/ao-tools/altosui/AltosCSVUI.java b/ao-tools/altosui/AltosCSVUI.java index 643d4112..4eb72de8 100644 --- a/ao-tools/altosui/AltosCSVUI.java +++ b/ao-tools/altosui/AltosCSVUI.java @@ -35,17 +35,17 @@ public class AltosCSVUI extends JDialog implements Runnable, ActionListener { - JFrame frame; - Thread thread; - AltosReader reader; - AltosCSV writer; + JFrame frame; + Thread thread; + AltosRecordIterable iterable; + AltosCSV writer; public void run() { AltosLogfileChooser chooser; chooser = new AltosLogfileChooser(frame); - reader = chooser.runDialog(); - if (reader == null) + iterable = chooser.runDialog(); + if (iterable == null) return; JFileChooser csv_chooser; @@ -67,8 +67,7 @@ public class AltosCSVUI "Cannot open file", JOptionPane.ERROR_MESSAGE); } - writer.write(reader); - reader.close(); + writer.write(iterable); writer.close(); } } diff --git a/ao-tools/altosui/AltosDisplayThread.java b/ao-tools/altosui/AltosDisplayThread.java new file mode 100644 index 00000000..9cc3d5ce --- /dev/null +++ b/ao-tools/altosui/AltosDisplayThread.java @@ -0,0 +1,255 @@ +/* + * 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; + int crc_errors; + AltosStatusTable flightStatus; + AltosInfoTable flightInfo; + + 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 %d, elevation %d, range %d.\n", + (int) (state.height + 0.5), + (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; + } + } + + long now () { + return System.currentTimeMillis(); + } + + void set_report_time() { + report_time = now() + report_interval; + } + + public void run () { + + reported_landing = 0; + state = null; + report_interval = 10000; + 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(); + } + } + + void init() { } + + AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; } + + void close(boolean interrupted) { } + + void update(AltosState state) throws InterruptedException { } + + 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; + } + + void show(AltosState state, int crc_errors) { + if (state != null) { + flightStatus.set(state); + flightInfo.show(state, crc_errors); + } + } + + public void run() { + boolean interrupted = false; + String line; + AltosState state = null; + AltosState old_state = null; + boolean told; + + idle_thread = new IdleThread(); + + flightInfo.clear(); + try { + for (;;) { + try { + AltosRecord record = read(); + if (record == null) + break; + old_state = state; + state = new AltosState(record, state); + 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 { + close(interrupted); + idle_thread.interrupt(); + try { + idle_thread.join(); + } catch (InterruptedException ie) {} + } + } + + public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosStatusTable in_status, AltosInfoTable in_info) { + parent = in_parent; + voice = in_voice; + flightStatus = in_status; + flightInfo = in_info; + } + + public void report() { + if (idle_thread != null) + idle_thread.report(true); + } + +} diff --git a/ao-tools/altosui/AltosEepromReader.java b/ao-tools/altosui/AltosEepromIterable.java index 03e73812..d4ac3f3e 100644 --- a/ao-tools/altosui/AltosEepromReader.java +++ b/ao-tools/altosui/AltosEepromIterable.java @@ -69,7 +69,7 @@ class AltosOrderedRecord extends AltosEepromRecord implements Comparable<AltosOr } } -public class AltosEepromReader extends AltosReader { +public class AltosEepromIterable extends AltosRecordIterable { static final int seen_flight = 1; static final int seen_sensor = 2; @@ -81,158 +81,178 @@ public class AltosEepromReader extends AltosReader { static final int seen_basic = seen_flight|seen_sensor|seen_temp_volt|seen_deploy; - AltosRecord state; - AltosOrderedRecord record; + AltosEepromRecord flight_record; + AltosEepromRecord gps_date_record; TreeSet<AltosOrderedRecord> records; - Iterator<AltosOrderedRecord> record_iterator; + LinkedList<AltosRecord> list; - int seen; + class EepromState { + int seen; + int n_pad_samples; + double ground_pres; + int gps_tick; + int boost_tick; - int index; - - boolean last_reported; - - double ground_pres; - - int n_pad_samples; - - int gps_tick; - - int boost_tick; - - boolean saw_gps_date; + EepromState() { + seen = 0; + n_pad_samples = 0; + ground_pres = 0.0; + gps_tick = 0; + } + } - boolean missing_gps_time; + 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; + } + } - public AltosRecord read() throws IOException, ParseException { - for (;;) { - if (record == null) { - if (!record_iterator.hasNext()) { - if (last_reported) - return null; - last_reported = true; - AltosRecord r = new AltosRecord(state); - r.time = (r.tick - boost_tick) / 100.0; - return r; - } - record = record_iterator.next(); + 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(); - if ((seen & seen_basic) == seen_basic && record.tick != state.tick) { - AltosRecord r = new AltosRecord(state); - r.time = (r.tick - boost_tick) / 100.0; - return r; - } - } + state.state = Altos.ao_flight_pad; + state.accel_plus_g = 15758; + state.accel_minus_g = 16294; - state.tick = record.tick; - switch (record.cmd) { - case Altos.AO_LOG_FLIGHT: - /* recorded when first read from the file */ - break; - case Altos.AO_LOG_SENSOR: - state.accel = record.a; - state.pres = record.b; - if (state.state < Altos.ao_flight_boost) { - n_pad_samples++; - ground_pres += state.pres; - state.ground_pres = (int) (ground_pres / 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); - } - seen |= seen_sensor; - break; - case Altos.AO_LOG_TEMP_VOLT: - state.temp = record.a; - state.batt = record.b; - seen |= seen_temp_volt; - break; - case Altos.AO_LOG_DEPLOY: - state.drogue = record.a; - state.main = record.b; - seen |= seen_deploy; - break; - case Altos.AO_LOG_STATE: - state.state = record.a; - break; - case Altos.AO_LOG_GPS_TIME: - 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 == 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; + /* 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); - 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; + 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); } - record = null; + 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) { @@ -313,18 +333,13 @@ public class AltosEepromReader extends AltosReader { * matching the first packet out of the GPS unit but not * written until the final GPS packet has been received. */ - public AltosEepromReader (FileInputStream input) { - state = new AltosRecord(); - state.state = Altos.ao_flight_pad; - state.accel_plus_g = 15758; - state.accel_minus_g = 16294; - seen = 0; + public AltosEepromIterable (FileInputStream input) { records = new TreeSet<AltosOrderedRecord>(); AltosOrderedRecord last_gps_time = null; int index = 0; - int tick = 0; + int prev_tick = 0; boolean missing_time = false; @@ -333,18 +348,15 @@ public class AltosEepromReader extends AltosReader { String line = AltosRecord.gets(input); if (line == null) break; - AltosOrderedRecord record = new AltosOrderedRecord(line, index++, tick); + AltosOrderedRecord record = new AltosOrderedRecord(line, index++, prev_tick); if (record == null) break; if (record.cmd == Altos.AO_LOG_INVALID) continue; - tick = record.tick; + prev_tick = record.tick; if (record.cmd == Altos.AO_LOG_FLIGHT) { - state.ground_accel = record.a; - state.flight_accel = record.a; - state.flight = record.b; - boost_tick = tick; - seen |= seen_flight; + flight_record = record; + continue; } /* Two firmware bugs caused the loss of some GPS data. @@ -353,8 +365,10 @@ public class AltosEepromReader extends AltosReader { * record. Detect the loss of the GPS date and fix up the * missing time records */ - if (record.cmd == Altos.AO_LOG_GPS_DATE) - saw_gps_date = true; + 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) { @@ -397,7 +411,6 @@ public class AltosEepromReader extends AltosReader { } catch (IOException io) { } catch (ParseException pe) { } - record_iterator = records.iterator(); try { input.close(); } catch (IOException ie) { diff --git a/ao-tools/altosui/AltosLogfileChooser.java b/ao-tools/altosui/AltosLogfileChooser.java index 36b51de6..8b9d77d6 100644 --- a/ao-tools/altosui/AltosLogfileChooser.java +++ b/ao-tools/altosui/AltosLogfileChooser.java @@ -27,11 +27,6 @@ import java.util.*; import java.text.*; import java.util.prefs.*; -import altosui.AltosPreferences; -import altosui.AltosReader; -import altosui.AltosEepromReader; -import altosui.AltosTelemetryReader; - public class AltosLogfileChooser extends JFileChooser { JFrame frame; String filename; @@ -45,7 +40,7 @@ public class AltosLogfileChooser extends JFileChooser { return file; } - public AltosReader runDialog() { + public AltosRecordIterable runDialog() { int ret; ret = showOpenDialog(frame); @@ -59,9 +54,9 @@ public class AltosLogfileChooser extends JFileChooser { in = new FileInputStream(file); if (filename.endsWith("eeprom")) - return new AltosEepromReader(in); + return new AltosEepromIterable(in); else - return new AltosTelemetryReader(in); + return new AltosTelemetryIterable(in); } catch (FileNotFoundException fe) { JOptionPane.showMessageDialog(frame, filename, diff --git a/ao-tools/altosui/AltosRecord.java b/ao-tools/altosui/AltosRecord.java index 18c6079d..00484767 100644 --- a/ao-tools/altosui/AltosRecord.java +++ b/ao-tools/altosui/AltosRecord.java @@ -142,7 +142,7 @@ public class AltosRecord { return counts_per_g / 9.80665; } public double acceleration() { - return (accel_plus_g - accel) / accel_counts_per_mss(); + return (ground_accel - accel) / accel_counts_per_mss(); } public double accel_speed() { diff --git a/ao-tools/altosui/AltosRecordIterable.java b/ao-tools/altosui/AltosRecordIterable.java new file mode 100644 index 00000000..147ecc14 --- /dev/null +++ b/ao-tools/altosui/AltosRecordIterable.java @@ -0,0 +1,42 @@ +/* + * 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 altosui.AltosRecord; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosEepromMonitor; + +public abstract class AltosRecordIterable implements Iterable<AltosRecord> { + public abstract Iterator<AltosRecord> iterator(); + public void write_comments(PrintStream out) { } +} diff --git a/ao-tools/altosui/AltosReplayThread.java b/ao-tools/altosui/AltosReplayThread.java new file mode 100644 index 00000000..b418160a --- /dev/null +++ b/ao-tools/altosui/AltosReplayThread.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package altosui; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.*; +import java.io.*; +import java.util.*; +import java.text.*; +import java.util.prefs.*; +import java.util.concurrent.LinkedBlockingQueue; + +import altosui.Altos; +import altosui.AltosSerial; +import altosui.AltosSerialMonitor; +import altosui.AltosRecord; +import altosui.AltosTelemetry; +import altosui.AltosState; +import altosui.AltosDeviceDialog; +import altosui.AltosPreferences; +import altosui.AltosLog; +import altosui.AltosVoice; +import altosui.AltosFlightInfoTableModel; +import altosui.AltosChannelMenu; +import altosui.AltosFlashUI; +import altosui.AltosLogfileChooser; +import altosui.AltosCSVUI; +import altosui.AltosLine; +import altosui.AltosStatusTable; +import altosui.AltosInfoTable; +import altosui.AltosDisplayThread; + +/* + * Open an existing telemetry file and replay it in realtime + */ + +public class AltosReplayThread extends AltosDisplayThread { + Iterator<AltosRecord> iterator; + String name; + + public AltosRecord read() { + if (iterator.hasNext()) + return iterator.next(); + return null; + } + + public void close (boolean interrupted) { + if (!interrupted) + report(); + } + + 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 AltosReplayThread(Frame parent, Iterator<AltosRecord> in_iterator, + String in_name, AltosVoice voice, + AltosStatusTable status, AltosInfoTable info) { + super(parent, voice, status, info); + iterator = in_iterator; + name = in_name; + } +} diff --git a/ao-tools/altosui/AltosTelemetryReader.java b/ao-tools/altosui/AltosTelemetryIterable.java index 3564a0a5..0a125c98 100644 --- a/ao-tools/altosui/AltosTelemetryReader.java +++ b/ao-tools/altosui/AltosTelemetryIterable.java @@ -22,26 +22,17 @@ import java.util.*; import java.text.*; import altosui.AltosTelemetry; -public class AltosTelemetryReader extends AltosReader { +public class AltosTelemetryIterable extends AltosRecordIterable { LinkedList<AltosRecord> records; - Iterator<AltosRecord> record_iterator; - - int boost_tick; - - public AltosRecord read() throws IOException, ParseException { - AltosRecord r; - if (!record_iterator.hasNext()) - return null; - - r = record_iterator.next(); - r.time = (r.tick - boost_tick) / 100.0; - return r; + public Iterator<AltosRecord> iterator () { + return records.iterator(); } - public AltosTelemetryReader (FileInputStream input) { + public AltosTelemetryIterable (FileInputStream input) { boolean saw_boost = false; int current_tick = 0; + int boost_tick = 0; records = new LinkedList<AltosRecord> (); @@ -79,7 +70,11 @@ public class AltosTelemetryReader extends AltosReader { } catch (IOException io) { System.out.printf("io exception\n"); } - record_iterator = records.iterator(); + + /* 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/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java index 1adeeccf..29eda2ec 100644 --- a/ao-tools/altosui/AltosUI.java +++ b/ao-tools/altosui/AltosUI.java @@ -46,6 +46,7 @@ import altosui.AltosCSVUI; import altosui.AltosLine; import altosui.AltosStatusTable; import altosui.AltosInfoTable; +import altosui.AltosDisplayThread; import libaltosJNI.*; @@ -119,222 +120,7 @@ public class AltosUI extends JFrame { voice.speak("Rocket flight monitor ready."); } - void show(AltosState state, int crc_errors) { - if (state != null) { - flightStatus.set(state); - flightInfo.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 %d, elevation %d, range %d.\n", - (int) (state.height + 0.5), - (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; - } - } - - long now () { - return System.currentTimeMillis(); - } - - void set_report_time() { - report_time = now() + report_interval; - } - - public void run () { - - reported_landing = 0; - state = null; - report_interval = 10000; - 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(); - } - } - - private 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; - } - - class DisplayThread extends Thread { - IdleThread idle_thread; - - String name; - - int crc_errors; - - void init() { } - - AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; } - - void close(boolean interrupted) { } - - void update(AltosState state) throws InterruptedException { } - - public void run() { - boolean interrupted = false; - String line; - AltosState state = null; - AltosState old_state = null; - boolean told; - - idle_thread = new IdleThread(); - - flightInfo.clear(); - try { - for (;;) { - try { - AltosRecord record = read(); - if (record == null) - break; - old_state = state; - state = new AltosState(record, state); - 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(AltosUI.this, - String.format("Error reading from \"%s\"", name), - "Telemetry Read Error", - JOptionPane.ERROR_MESSAGE); - } finally { - close(interrupted); - idle_thread.interrupt(); - try { - idle_thread.join(); - } catch (InterruptedException ie) {} - } - } - - public void report() { - if (idle_thread != null) - idle_thread.report(true); - } - } - - class DeviceThread extends DisplayThread { + class DeviceThread extends AltosDisplayThread { AltosSerial serial; LinkedBlockingQueue<AltosLine> telem; @@ -350,7 +136,8 @@ public class AltosUI extends JFrame { serial.remove_monitor(telem); } - public DeviceThread(AltosSerial s, String in_name) { + public DeviceThread(AltosSerial s, String in_name, AltosVoice voice, AltosStatusTable status, AltosInfoTable info) { + super(AltosUI.this, voice, status, info); serial = s; telem = new LinkedBlockingQueue<AltosLine>(); serial.add_monitor(telem); @@ -366,7 +153,7 @@ public class AltosUI extends JFrame { try { stop_display(); serial_line.open(device); - DeviceThread thread = new DeviceThread(serial_line, device.getPath()); + DeviceThread thread = new DeviceThread(serial_line, device.getPath(), voice, flightStatus, flightInfo); serial_line.set_channel(AltosPreferences.channel()); serial_line.set_callsign(AltosPreferences.callsign()); run_display(thread); @@ -409,41 +196,6 @@ public class AltosUI extends JFrame { new AltosFlashUI(AltosUI.this); } - /* - * Open an existing telemetry file and replay it in realtime - */ - - class ReplayThread extends DisplayThread { - AltosReader reader; - String name; - - public AltosRecord read() { - try { - return reader.read(); - } catch (IOException ie) { - JOptionPane.showMessageDialog(AltosUI.this, - name, - "error reading", - JOptionPane.ERROR_MESSAGE); - } catch (ParseException pe) { - } - return null; - } - - public void close (boolean interrupted) { - if (!interrupted) - report(); - } - - public ReplayThread(AltosReader in_reader, String in_name) { - reader = in_reader; - } - 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)); - } - } Thread display_thread; @@ -469,10 +221,13 @@ public class AltosUI extends JFrame { private void Replay() { AltosLogfileChooser chooser = new AltosLogfileChooser( AltosUI.this); - AltosReader reader = chooser.runDialog(); - if (reader != null) - run_display(new ReplayThread(reader, - chooser.filename())); + AltosRecordIterable iterable = chooser.runDialog(); + if (iterable != null) + run_display(new AltosReplayThread(this, iterable.iterator(), + chooser.filename(), + voice, + flightStatus, + flightInfo)); } /* Connect to TeleMetrum, either directly or through @@ -663,16 +418,16 @@ public class AltosUI extends JFrame { return input.concat(extension); } - static AltosReader open_logfile(String filename) { + static AltosRecordIterable open_logfile(String filename) { File file = new File (filename); try { FileInputStream in; in = new FileInputStream(file); if (filename.endsWith("eeprom")) - return new AltosEepromReader(in); + return new AltosEepromIterable(in); else - return new AltosTelemetryReader(in); + return new AltosTelemetryIterable(in); } catch (FileNotFoundException fe) { System.out.printf("Cannot open '%s'\n", filename); return null; @@ -696,14 +451,13 @@ public class AltosUI extends JFrame { return; } System.out.printf("Processing \"%s\" to \"%s\"\n", input, output); - AltosReader reader = open_logfile(input); - if (reader == null) + AltosRecordIterable iterable = open_logfile(input); + if (iterable == null) return; AltosCSV writer = open_csv(output); if (writer == null) return; - writer.write(reader); - reader.close(); + writer.write(iterable); writer.close(); } diff --git a/ao-tools/altosui/Makefile.am b/ao-tools/altosui/Makefile.am index 56ac0520..7070af22 100644 --- a/ao-tools/altosui/Makefile.am +++ b/ao-tools/altosui/Makefile.am @@ -20,9 +20,10 @@ altosui_JAVA = \ AltosDebug.java \ AltosDeviceDialog.java \ AltosDevice.java \ + AltosDisplayThread.java \ AltosEepromDownload.java \ AltosEepromMonitor.java \ - AltosEepromReader.java \ + AltosEepromIterable.java \ AltosEepromRecord.java \ AltosFile.java \ AltosFlash.java \ @@ -41,6 +42,8 @@ altosui_JAVA = \ AltosPreferences.java \ AltosReader.java \ AltosRecord.java \ + AltosRecordIterable.java \ + AltosReplayThread.java \ AltosRomconfig.java \ AltosRomconfigUI.java \ AltosSerial.java \ @@ -48,7 +51,7 @@ altosui_JAVA = \ AltosState.java \ AltosStatusTable.java \ AltosTelemetry.java \ - AltosTelemetryReader.java \ + AltosTelemetryIterable.java \ AltosUI.java \ AltosVoice.java |