From: Keith Packard Date: Tue, 28 Sep 2010 00:11:48 +0000 (-0700) Subject: altosui: Create iterables for log file scanning. Split out display threads X-Git-Tag: debian/0.7.1+28+gd8a2f4c~6^2~2 X-Git-Url: https://git.gag.com/?p=fw%2Faltos;a=commitdiff_plain;h=c89a34d1eb25155405b0036baeadc7bbfeade1c2;hp=e66919aa46193bd8c7a1e86fb32a3367dae121f5 altosui: Create iterables for log file scanning. Split out display threads Convert from log file reading paradigm to using iterators which is more idiomatic for java. Split more code out of AltosUI.java, including the display update threads for telemetry monitoring and logfile replay.x Signed-off-by: Keith Packard --- 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 + * + * 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/AltosEepromIterable.java b/ao-tools/altosui/AltosEepromIterable.java new file mode 100644 index 00000000..d4ac3f3e --- /dev/null +++ b/ao-tools/altosui/AltosEepromIterable.java @@ -0,0 +1,419 @@ +/* + * Copyright © 2010 Keith Packard + * + * 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; + +/* + * 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 { + + public int index; + + public AltosOrderedRecord(String line, int in_index, int prev_tick) + throws ParseException { + super(line); + int new_tick = tick | (prev_tick & ~0xffff); + if (new_tick < prev_tick) { + if (prev_tick - new_tick > 0x8000) + new_tick += 0x10000; + } + tick = new_tick; + 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 records; + + LinkedList 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 make_list() { + LinkedList list = new LinkedList(); + Iterator 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 iterator() { + if (list == null) + list = make_list(); + return list.iterator(); + } + + public void write_comments(PrintStream out) { + Iterator 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; + + 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 last_gps_time = null; + + int index = 0; + int prev_tick = 0; + + boolean missing_time = false; + + try { + for (;;) { + String line = AltosRecord.gets(input); + if (line == null) + break; + AltosOrderedRecord record = new AltosOrderedRecord(line, index++, prev_tick); + if (record == null) + break; + if (record.cmd == Altos.AO_LOG_INVALID) + continue; + prev_tick = record.tick; + 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 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/ao-tools/altosui/AltosEepromReader.java b/ao-tools/altosui/AltosEepromReader.java deleted file mode 100644 index 03e73812..00000000 --- a/ao-tools/altosui/AltosEepromReader.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * 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; - -/* - * 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 { - - public int index; - - public AltosOrderedRecord(String line, int in_index, int prev_tick) - throws ParseException { - super(line); - int new_tick = tick | (prev_tick & ~0xffff); - if (new_tick < prev_tick) { - if (prev_tick - new_tick > 0x8000) - new_tick += 0x10000; - } - tick = new_tick; - 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 AltosEepromReader extends AltosReader { - - 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; - - AltosRecord state; - AltosOrderedRecord record; - - TreeSet records; - - Iterator record_iterator; - - int seen; - - int index; - - boolean last_reported; - - double ground_pres; - - int n_pad_samples; - - int gps_tick; - - int boost_tick; - - boolean saw_gps_date; - - boolean missing_gps_time; - - 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(); - - 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.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; - - 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; - } - record = null; - } - } - - public void write_comments(PrintStream out) { - Iterator 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; - - 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 AltosEepromReader (FileInputStream input) { - state = new AltosRecord(); - state.state = Altos.ao_flight_pad; - state.accel_plus_g = 15758; - state.accel_minus_g = 16294; - seen = 0; - records = new TreeSet(); - - AltosOrderedRecord last_gps_time = null; - - int index = 0; - int tick = 0; - - boolean missing_time = false; - - try { - for (;;) { - String line = AltosRecord.gets(input); - if (line == null) - break; - AltosOrderedRecord record = new AltosOrderedRecord(line, index++, tick); - if (record == null) - break; - if (record.cmd == Altos.AO_LOG_INVALID) - continue; - 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; - } - - /* 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) - saw_gps_date = true; - - /* 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 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) { - } - 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 + * + * 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 { + public abstract Iterator 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 + * + * 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 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 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/AltosTelemetryIterable.java b/ao-tools/altosui/AltosTelemetryIterable.java new file mode 100644 index 00000000..0a125c98 --- /dev/null +++ b/ao-tools/altosui/AltosTelemetryIterable.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2010 Keith Packard + * + * 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 altosui.AltosTelemetry; + +public class AltosTelemetryIterable extends AltosRecordIterable { + LinkedList records; + + public Iterator iterator () { + return records.iterator(); + } + + public AltosTelemetryIterable (FileInputStream input) { + boolean saw_boost = false; + int current_tick = 0; + int boost_tick = 0; + + records = new LinkedList (); + + 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/ao-tools/altosui/AltosTelemetryReader.java b/ao-tools/altosui/AltosTelemetryReader.java deleted file mode 100644 index 3564a0a5..00000000 --- a/ao-tools/altosui/AltosTelemetryReader.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2010 Keith Packard - * - * 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 altosui.AltosTelemetry; - -public class AltosTelemetryReader extends AltosReader { - LinkedList records; - - Iterator 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 AltosTelemetryReader (FileInputStream input) { - boolean saw_boost = false; - int current_tick = 0; - - records = new LinkedList (); - - 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"); - } - record_iterator = records.iterator(); - 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 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(); 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