altosui: Create iterables for log file scanning. Split out display threads
authorKeith Packard <keithp@keithp.com>
Tue, 28 Sep 2010 00:11:48 +0000 (17:11 -0700)
committerKeith Packard <keithp@keithp.com>
Tue, 28 Sep 2010 00:11:48 +0000 (17:11 -0700)
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 <keithp@keithp.com>
13 files changed:
ao-tools/altosui/AltosCSV.java
ao-tools/altosui/AltosCSVUI.java
ao-tools/altosui/AltosDisplayThread.java [new file with mode: 0644]
ao-tools/altosui/AltosEepromIterable.java [new file with mode: 0644]
ao-tools/altosui/AltosEepromReader.java [deleted file]
ao-tools/altosui/AltosLogfileChooser.java
ao-tools/altosui/AltosRecord.java
ao-tools/altosui/AltosRecordIterable.java [new file with mode: 0644]
ao-tools/altosui/AltosReplayThread.java [new file with mode: 0644]
ao-tools/altosui/AltosTelemetryIterable.java [new file with mode: 0644]
ao-tools/altosui/AltosTelemetryReader.java [deleted file]
ao-tools/altosui/AltosUI.java
ao-tools/altosui/Makefile.am

index f7b3c03..7f14ada 100644 (file)
@@ -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 {
index 643d411..4eb72de 100644 (file)
@@ -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 (file)
index 0000000..9cc3d5c
--- /dev/null
@@ -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/AltosEepromIterable.java b/ao-tools/altosui/AltosEepromIterable.java
new file mode 100644 (file)
index 0000000..d4ac3f3
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * 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;
+
+/*
+ * 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)
+               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<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;
+
+               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 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<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/ao-tools/altosui/AltosEepromReader.java b/ao-tools/altosui/AltosEepromReader.java
deleted file mode 100644 (file)
index 03e7381..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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;
-
-/*
- * 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)
-               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<AltosOrderedRecord>     records;
-
-       Iterator<AltosOrderedRecord>                    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<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;
-
-               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>();
-
-               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<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) {
-               }
-               record_iterator = records.iterator();
-               try {
-                       input.close();
-               } catch (IOException ie) {
-               }
-       }
-}
index 36b51de..8b9d77d 100644 (file)
@@ -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,
index 18c6079..0048476 100644 (file)
@@ -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 (file)
index 0000000..147ecc1
--- /dev/null
@@ -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 (file)
index 0000000..b418160
--- /dev/null
@@ -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/AltosTelemetryIterable.java b/ao-tools/altosui/AltosTelemetryIterable.java
new file mode 100644 (file)
index 0000000..0a125c9
--- /dev/null
@@ -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.io.*;
+import java.util.*;
+import java.text.*;
+import altosui.AltosTelemetry;
+
+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/ao-tools/altosui/AltosTelemetryReader.java b/ao-tools/altosui/AltosTelemetryReader.java
deleted file mode 100644 (file)
index 3564a0a..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 altosui.AltosTelemetry;
-
-public class AltosTelemetryReader extends AltosReader {
-       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 AltosTelemetryReader (FileInputStream input) {
-               boolean saw_boost = false;
-               int     current_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");
-               }
-               record_iterator = records.iterator();
-               try {
-                       input.close();
-               } catch (IOException ie) {
-               }
-       }
-}
index 1adeecc..29eda2e 100644 (file)
@@ -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();
        }
 
index 56ac052..7070af2 100644 (file)
@@ -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