altoslib,altosuilib,altosui: Get stats and replay working again.
authorKeith Packard <keithp@keithp.com>
Fri, 26 May 2017 07:20:17 +0000 (00:20 -0700)
committerKeith Packard <keithp@keithp.com>
Fri, 26 May 2017 07:20:17 +0000 (00:20 -0700)
Stats are really easy with all of the data in memory.
Replay takes a special thread to run the data and dump it into a
single state.

Signed-off-by: Keith Packard <keithp@keithp.com>
altoslib/AltosEepromRecordMega.java
altoslib/AltosFlightReader.java
altoslib/AltosFlightSeries.java
altoslib/AltosFlightStats.java
altoslib/AltosReplayReader.java
altoslib/AltosTelemetryFile.java
altoslib/AltosTimeSeries.java
altosui/AltosFlightUI.java
altosui/AltosUI.java
altosuilib/AltosDisplayThread.java

index d4a5a0b23f282d911db0240db0b3fcf68ca2e77d..cd6916b456f5a938bec0ad83f246240dd08ae941 100644 (file)
@@ -143,7 +143,6 @@ public class AltosEepromRecordMega extends AltosEepromRecord {
                                               ground_yaw() / 512.0);
                        break;
                case AltosLib.AO_LOG_STATE:
                                               ground_yaw() / 512.0);
                        break;
                case AltosLib.AO_LOG_STATE:
-                       System.out.printf("log state %s\n", AltosLib.state_name(state()));
                        listener.set_state(state());
                        break;
                case AltosLib.AO_LOG_SENSOR:
                        listener.set_state(state());
                        break;
                case AltosLib.AO_LOG_SENSOR:
index 250e2236b7a555a5fb2173f0bc6c925ae563e9bf..790710e02805b80d715ed4942c1e0af377e13776 100644 (file)
@@ -45,8 +45,6 @@ public abstract class AltosFlightReader {
 
        public void save_telemetry_rate() { }
 
 
        public void save_telemetry_rate() { }
 
-       public void update(AltosState state) throws InterruptedException { }
-
        public boolean supports_telemetry(int telemetry) { return false; }
 
        public boolean supports_telemetry_rate(int telemetry_rate) { return false; }
        public boolean supports_telemetry(int telemetry) { return false; }
 
        public boolean supports_telemetry_rate(int telemetry_rate) { return false; }
index 0eea34b7e72ac996ac5a5d78beb308c35c2774c5..7bedf38903eeadb209cc7616220a02a049795c45 100644 (file)
@@ -136,7 +136,6 @@ public class AltosFlightSeries extends AltosDataListener {
                        state_series = add_series(state_name, AltosConvert.state_name);
                else if (this.state == state)
                        return;
                        state_series = add_series(state_name, AltosConvert.state_name);
                else if (this.state == state)
                        return;
-               System.out.printf("state %s\n", AltosLib.state_name(state));
                this.state = state;
                state_series.add(time(), state);
        }
                this.state = state;
                state_series.add(time(), state);
        }
@@ -147,7 +146,6 @@ public class AltosFlightSeries extends AltosDataListener {
 
        public void set_acceleration(double acceleration) {
                if (accel_series == null) {
 
        public void set_acceleration(double acceleration) {
                if (accel_series == null) {
-                       System.out.printf("set acceleration %g\n", acceleration);
                        accel_series = add_series(accel_name, AltosConvert.accel);
                }
                accel_series.add(time(), acceleration);
                        accel_series = add_series(accel_name, AltosConvert.accel);
                }
                accel_series.add(time(), acceleration);
@@ -221,10 +219,8 @@ public class AltosFlightSeries extends AltosDataListener {
        public static final String speed_name = "Speed";
 
        private void compute_speed() {
        public static final String speed_name = "Speed";
 
        private void compute_speed() {
-               if (speed_series != null) {
-                       System.out.printf("speed series already made\n");
+               if (speed_series != null)
                        return;
                        return;
-               }
 
                AltosTimeSeries alt_speed_series = null;
                AltosTimeSeries accel_speed_series = null;
 
                AltosTimeSeries alt_speed_series = null;
                AltosTimeSeries accel_speed_series = null;
@@ -235,8 +231,6 @@ public class AltosFlightSeries extends AltosDataListener {
 
                        alt_speed_series = make_series(speed_name, AltosConvert.speed);
                        temp_series.filter(alt_speed_series, 10.0);
 
                        alt_speed_series = make_series(speed_name, AltosConvert.speed);
                        temp_series.filter(alt_speed_series, 10.0);
-               } else {
-                       System.out.printf("no altitude series\n");
                }
                if (accel_series != null) {
                        AltosTimeSeries temp_series = make_series(speed_name, AltosConvert.speed);
                }
                if (accel_series != null) {
                        AltosTimeSeries temp_series = make_series(speed_name, AltosConvert.speed);
@@ -244,8 +238,6 @@ public class AltosFlightSeries extends AltosDataListener {
 
                        accel_speed_series = make_series(speed_name, AltosConvert.speed);
                        temp_series.filter(accel_speed_series, 0.1);
 
                        accel_speed_series = make_series(speed_name, AltosConvert.speed);
                        temp_series.filter(accel_speed_series, 0.1);
-               } else {
-                       System.out.printf("no accel series\n");
                }
 
                if (alt_speed_series != null && accel_speed_series != null) {
                }
 
                if (alt_speed_series != null && accel_speed_series != null) {
@@ -277,11 +269,8 @@ public class AltosFlightSeries extends AltosDataListener {
                } else if (accel_speed_series != null) {
                        speed_series = accel_speed_series;
                }
                } else if (accel_speed_series != null) {
                        speed_series = accel_speed_series;
                }
-               if (speed_series != null) {
+               if (speed_series != null)
                        add_series(speed_series);
                        add_series(speed_series);
-                       System.out.printf("speed series for %s set to %s\n", this.toString(), speed_series.toString());
-               } else
-                       System.out.printf("didn't manage to make speed series\n");
        }
 
        AltosTimeSeries kalman_height_series, kalman_speed_series, kalman_accel_series;
        }
 
        AltosTimeSeries kalman_height_series, kalman_speed_series, kalman_accel_series;
@@ -575,7 +564,6 @@ public class AltosFlightSeries extends AltosDataListener {
        }
 
        public void fill_in() {
        }
 
        public void fill_in() {
-               System.out.printf("fill in %s\n", this.toString());
                compute_speed();
                compute_accel();
                if (cal_data.ground_altitude != AltosLib.MISSING)
                compute_speed();
                compute_accel();
                if (cal_data.ground_altitude != AltosLib.MISSING)
@@ -594,7 +582,6 @@ public class AltosFlightSeries extends AltosDataListener {
 
        public AltosFlightSeries(AltosCalData cal_data) {
                super(cal_data);
 
        public AltosFlightSeries(AltosCalData cal_data) {
                super(cal_data);
-               System.out.printf("new flight series %s\n", this.toString());
                init();
        }
 }
                init();
        }
 }
index 54d0dd6361ae0e62b46005402ad504d99bfb79b4..2948ad380e05801a741dae247b245250dfb25a99 100644 (file)
@@ -115,8 +115,6 @@ public class AltosFlightStats {
                double          end_time = 0;
                double          landed_time = landed_time(series);
 
                double          end_time = 0;
                double          landed_time = landed_time(series);
 
-               System.out.printf("flight stats %s\n", series.toString());
-
                year = month = day = AltosLib.MISSING;
                hour = minute = second = AltosLib.MISSING;
                serial = flight = AltosLib.MISSING;
                year = month = day = AltosLib.MISSING;
                hour = minute = second = AltosLib.MISSING;
                serial = flight = AltosLib.MISSING;
@@ -133,8 +131,21 @@ public class AltosFlightStats {
 
                for (int s = AltosLib.ao_flight_startup; s <= AltosLib.ao_flight_landed; s++) {
                        state_count[s] = 0;
 
                for (int s = AltosLib.ao_flight_startup; s <= AltosLib.ao_flight_landed; s++) {
                        state_count[s] = 0;
-                       state_speed[s] = 0.0;
-                       state_accel[s] = 0.0;
+
+                       if (s == AltosLib.ao_flight_boost)
+                               state_start[s] = boost_time;
+                       else
+                               state_start[s] = series.state_series.time_of(s);
+                       if (s == AltosLib.ao_flight_landed)
+                               state_end[s] = landed_time;
+                       else
+                               state_end[s] = series.state_series.time_of(s+1);
+
+                       if (series.speed_series != null)
+                               state_speed[s] = series.speed_series.average(state_start[s], state_end[s]);
+
+                       if (series.accel_series != null)
+                               state_accel[s] = series.accel_series.average(state_start[s], state_end[s]);
                }
 
                serial = cal_data.serial;
                }
 
                serial = cal_data.serial;
@@ -145,92 +156,46 @@ public class AltosFlightStats {
                has_rssi = series.rssi_series != null;
                has_flight_data = series.pressure_series != null;
 
                has_rssi = series.rssi_series != null;
                has_flight_data = series.pressure_series != null;
 
-               if (series.gps_series != null) {
-                       AltosGPS gps = series.gps_series.get(0).gps;
+               AltosGPS gps = series.cal_data.gps_pad;
 
 
+               if (gps != null) {
                        year = gps.year;
                        month = gps.month;
                        day = gps.day;
                        hour = gps.hour;
                        minute = gps.minute;
                        second = gps.second;
                        year = gps.year;
                        month = gps.month;
                        day = gps.day;
                        hour = gps.hour;
                        minute = gps.minute;
                        second = gps.second;
+                       has_gps = true;
+                       lat = pad_lat = gps.lat;
+                       lon = pad_lon = gps.lon;
+                       for (AltosGPSTimeValue gtv : series.gps_series) {
+                               gps = gtv.gps;
+                               if (gps.locked && gps.nsat >= 4) {
+                                       lat = gps.lat;
+                                       lon = gps.lon;
+                               }
+                       }
+
                }
 
                max_height = AltosLib.MISSING;
                if (series.height_series != null)
                        max_height = series.height_series.max();
                max_speed = AltosLib.MISSING;
                }
 
                max_height = AltosLib.MISSING;
                if (series.height_series != null)
                        max_height = series.height_series.max();
                max_speed = AltosLib.MISSING;
-               if (series.speed_series != null)
-                       max_speed = series.speed_series.max();
-               else
-                       System.out.printf("missing speed series\n");
+               if (series.speed_series != null) {
+                       max_speed = series.speed_series.max(state_start[AltosLib.ao_flight_boost], state_start[AltosLib.ao_flight_drogue]);
+                       if (max_speed == AltosLib.MISSING)
+                               max_speed = series.speed_series.max();
+               }
                max_acceleration = AltosLib.MISSING;
                max_acceleration = AltosLib.MISSING;
-               if (series.accel_series != null)
-                       max_acceleration = series.accel_series.max();
+               if (series.accel_series != null) {
+                       max_acceleration = series.accel_series.max(state_start[AltosLib.ao_flight_boost], state_start[AltosLib.ao_flight_drogue]);
+                       if (max_acceleration == AltosLib.MISSING)
+                               max_acceleration = series.accel_series.max();
+               }
                max_gps_height = AltosLib.MISSING;
                if (series.gps_height != null)
                        max_gps_height = series.gps_height.max();
 
                max_gps_height = AltosLib.MISSING;
                if (series.gps_height != null)
                        max_gps_height = series.gps_height.max();
 
-/*
-               for (AltosState state : states) {
-                       end_time = state.time;
-
-                       int state_id = state.state();
-                       if (boost_time != AltosLib.MISSING && state.time >= boost_time && state_id < AltosLib.ao_flight_boost) {
-                               state_id = AltosLib.ao_flight_boost;
-                       }
-                       if (landed_time != AltosLib.MISSING && state.time >= landed_time && state_id < AltosLib.ao_flight_landed) {
-                               state_id = AltosLib.ao_flight_landed;
-                       }
-
-                       if (0 <= state_id && state_id < AltosLib.ao_flight_invalid) {
-                               double acceleration = state.acceleration();
-                               double speed = state.speed();
-                               if (acceleration != AltosLib.MISSING && speed != AltosLib.MISSING) {
-                                       state_accel[state_id] += acceleration;
-                                       state_speed[state_id] += speed;
-                                       state_count[state_id]++;
-                               }
-                               if (state_start[state_id] == 0.0)
-                                       state_start[state_id] = state.time;
-                               if (state_end[state_id] < state.time)
-                                       state_end[state_id] = state.time;
-                       }
-                       if (state.pad_lat != AltosLib.MISSING) {
-                               pad_lat = state.pad_lat;
-                               pad_lon = state.pad_lon;
-                       }
-                       if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) {
-                               lat = state.gps.lat;
-                               lon = state.gps.lon;
-                               has_gps = true;
-                               if (state.gps.cc_gps_sat != null)
-                                       has_gps_sats = true;
-                               if (state.gps.course != AltosLib.MISSING)
-                                       has_gps_detail = true;
-                       }
-                       if (state.imu != null)
-                               has_imu = true;
-                       if (state.mag != null)
-                               has_mag = true;
-                       if (state.orient() != AltosLib.MISSING)
-                               has_orient = true;
-                       if (state.igniter_voltage != null && state.igniter_voltage.length > num_igniter)
-                               num_igniter = state.igniter_voltage.length;
-               }
-*/
-               for (int s = AltosLib.ao_flight_startup; s <= AltosLib.ao_flight_landed; s++) {
-                       if (state_count[s] > 0) {
-                               state_speed[s] /= state_count[s];
-                               state_accel[s] /= state_count[s];
-                       } else {
-                               state_speed[s] = AltosLib.MISSING;
-                               state_accel[s] = AltosLib.MISSING;
-                       }
-                       if (state_start[s] == 0)
-                               state_start[s] = end_time;
-                       if (state_end[s] == 0)
-                               state_end[s] = end_time;
-               }
        }
 }
        }
 }
index 59ade8719c8e33f4d133ddf04f4aaabc19aa95b6..fb8432e7e8049ed0132763d18a27c1520fcb8940 100644 (file)
@@ -20,36 +20,131 @@ package org.altusmetrum.altoslib_11;
 
 import java.io.*;
 import java.util.*;
 
 import java.io.*;
 import java.util.*;
+import java.util.concurrent.*;
 
 /*
  * Open an existing telemetry file and replay it in realtime
  */
 
 
 /*
  * Open an existing telemetry file and replay it in realtime
  */
 
+class AltosReplay extends AltosDataListener implements Runnable {
+
+       AltosState      state;
+       AltosRecordSet  record_set;
+       double          last_time = AltosLib.MISSING;
+       Semaphore       semaphore = new Semaphore(1);;
+       boolean         done = false;
+
+       public void set_time(double time) {
+               if (last_time != AltosLib.MISSING) {
+                       semaphore.release();
+                       double  delay = Math.min(time - last_time,10);
+                       if (delay > 0) {
+                               try {
+                                       Thread.sleep((int) (delay * 1000));
+                               } catch (InterruptedException ie) {
+                               }
+                       }
+               }
+               last_time = time;
+               super.set_time(time);
+               state.set_time(time);
+       }
+
+       public void set_state(int state) {
+               super.set_state(state);
+               this.state.set_state(state);
+       }
+
+       public void set_rssi(int rssi, int status) { state.set_rssi(rssi, status); }
+       public void set_received_time(long received_time) { }
+
+       public void set_acceleration(double accel) { state.set_acceleration(accel); }
+       public void set_pressure(double pa) { state.set_pressure(pa); }
+       public void set_thrust(double N) { state.set_thrust(N); }
+
+       public void set_kalman(double height, double speed, double accel) { state.set_kalman(height, speed, accel); }
+
+       public void set_temperature(double deg_c) { state.set_temperature(deg_c); }
+       public void set_battery_voltage(double volts) { state.set_battery_voltage(volts); }
+
+       public void set_apogee_voltage(double volts) { state.set_apogee_voltage(volts); }
+       public void set_main_voltage(double volts) { state.set_main_voltage(volts); }
+
+       public void set_gps(AltosGPS gps) { state.set_gps(gps); }
+
+       public void set_orient(double orient) { state.set_orient(orient); }
+       public void set_gyro(double roll, double pitch, double yaw) { state.set_gyro(roll, pitch, yaw); }
+       public void set_accel_ground(double along, double across, double through) { state.set_accel_ground(along, across, through); }
+       public void set_accel(double along, double across, double through) { state.set_accel(along, across, through); }
+       public void set_mag(double along, double across, double through) { state.set_mag(along, across, through); }
+       public void set_pyro_voltage(double volts) { state.set_pyro_voltage(volts); }
+       public void set_igniter_voltage(double[] voltage) { state.set_igniter_voltage(voltage); }
+       public void set_pyro_fired(int pyro_mask) { state.set_pyro_fired(pyro_mask); }
+       public void set_companion(AltosCompanion companion) { state.set_companion(companion); }
+
+       public void run () {
+               System.out.printf("ReplayReader running\n");
+               state = new AltosState(record_set.cal_data());
+
+               /* Tell the display that we're in pad mode */
+               state.set_state(AltosLib.ao_flight_pad);
+               semaphore.release();
+               try {
+                       Thread.sleep(100);
+               } catch (InterruptedException ie) {
+               }
+
+               /* Run the flight */
+               record_set.capture_series(this);
+
+               /* All done, signal that it's over */
+               done = true;
+               semaphore.release();
+       }
+
+       public AltosReplay(AltosRecordSet record_set) {
+               super(record_set.cal_data());
+               try {
+                       semaphore.acquire();
+               } catch (InterruptedException ie) { }
+               this.record_set = record_set;
+               Thread t = new Thread(this);
+               t.start();
+       }
+}
+
 public class AltosReplayReader extends AltosFlightReader {
 public class AltosReplayReader extends AltosFlightReader {
-       Iterator<AltosState>    iterator;
-       File    file;
+       File            file;
+       AltosReplay     replay;
 
        public AltosState read() {
 
        public AltosState read() {
-               if (iterator.hasNext())
-                       return iterator.next();
-               return null;
+
+               /* When done, let the display know */
+               if (replay.done)
+                       return null;
+
+               /* Wait for something to change */
+               try {
+                       replay.semaphore.acquire();
+               } catch (InterruptedException ie) {
+               }
+
+               /* Fake out the received time */
+               replay.state.set_received_time(System.currentTimeMillis());
+               return replay.state;
        }
 
        public void close (boolean interrupted) {
        }
 
        public void update(AltosState state) throws InterruptedException {
        }
 
        public void close (boolean interrupted) {
        }
 
        public void update(AltosState state) throws InterruptedException {
-               /* Make it run in realtime after the rocket leaves the pad */
-               if (state.state() > AltosLib.ao_flight_pad && state.time_change > 0)
-                       Thread.sleep((int) (Math.min(state.time_change,10) * 1000));
-               state.set_received_time(System.currentTimeMillis());
        }
 
        public File backing_file() { return file; }
 
        }
 
        public File backing_file() { return file; }
 
-       public AltosReplayReader(Iterator<AltosState> in_iterator, File in_file) {
-               iterator = in_iterator;
+       public AltosReplayReader(AltosRecordSet record_set, File in_file) {
                file = in_file;
                name = file.getName();
                file = in_file;
                name = file.getName();
+               replay = new AltosReplay(record_set);
        }
 }
        }
 }
index c6462872700f14e1f90fefcb194266a577082dc6..8adf7e695c569900ead1a3d301b547c5e0cce15a 100644 (file)
@@ -111,7 +111,6 @@ public class AltosTelemetryFile implements AltosRecordSet {
                                if (l.cal_data_complete())
                                        break;
                        }
                                if (l.cal_data_complete())
                                        break;
                        }
-                       System.out.printf("Telemetry boost tick %d\n", cal_data.boost_tick);
                }
                return cal_data;
        }
                }
                return cal_data;
        }
index 30b24d82bcf3503fe6f059e7ae022c512f700994..142c30ef2d794818b455b17ca59533b28cdf6533 100644 (file)
@@ -85,6 +85,16 @@ public class AltosTimeSeries implements Iterable<AltosTimeValue> {
                return values.get(after).value;
        }
 
                return values.get(after).value;
        }
 
+       public double time_of(double value) {
+               double  last = AltosLib.MISSING;
+               for (AltosTimeValue v : values) {
+                       if (v.value >= value)
+                               return v.time;
+                       last = v.time;
+               }
+               return last;
+       }
+
        public int size() {
                return values.size();
        }
        public int size() {
                return values.size();
        }
@@ -102,6 +112,16 @@ public class AltosTimeSeries implements Iterable<AltosTimeValue> {
                return max;
        }
 
                return max;
        }
 
+       public double max(double start_time, double end_time) {
+               double max = AltosLib.MISSING;
+               for (AltosTimeValue tv : values) {
+                       if (start_time <= tv.time && tv.time <= end_time)
+                               if (max == AltosLib.MISSING || tv.value > max)
+                                       max = tv.value;
+               }
+               return max;
+       }
+
        public double min() {
                double min = AltosLib.MISSING;
                for (AltosTimeValue tv : values) {
        public double min() {
                double min = AltosLib.MISSING;
                for (AltosTimeValue tv : values) {
@@ -111,6 +131,42 @@ public class AltosTimeSeries implements Iterable<AltosTimeValue> {
                return min;
        }
 
                return min;
        }
 
+       public double min(double start_time, double end_time) {
+               double min = AltosLib.MISSING;
+               for (AltosTimeValue tv : values) {
+                       if (start_time <= tv.time && tv.time <= end_time)
+                               if (min == AltosLib.MISSING || tv.value < min)
+                                       min = tv.value;
+               }
+               return min;
+       }
+
+       public double average() {
+               double total = 0;
+               int count = 0;
+               for (AltosTimeValue tv : values) {
+                       total += tv.value;
+                       count++;
+               }
+               if (count == 0)
+                       return AltosLib.MISSING;
+               return total / count;
+       }
+
+       public double average(double start_time, double end_time) {
+               double total = 0;
+               int count = 0;
+               for (AltosTimeValue tv : values) {
+                       if (start_time <= tv.time && tv.time <= end_time) {
+                               total += tv.value;
+                               count++;
+                       }
+               }
+               if (count == 0)
+                       return AltosLib.MISSING;
+               return total / count;
+       }
+
        public AltosTimeSeries integrate(AltosTimeSeries integral) {
                double  value = 0.0;
                double  pvalue = 0.0;
        public AltosTimeSeries integrate(AltosTimeSeries integral) {
                double  value = 0.0;
                double  pvalue = 0.0;
index eaf19256c57ff8bdffde39c029d5e337a746ecaf..23a62e958200771a14c09e452b45c442490e553c 100644 (file)
@@ -115,7 +115,9 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay {
 
                JComponent tab = which_tab(state);
                if (tab != cur_tab) {
 
                JComponent tab = which_tab(state);
                if (tab != cur_tab) {
+                       System.out.printf("checking tab for state %s\n", AltosLib.state_name(state.state()));
                        if (cur_tab == pane.getSelectedComponent()) {
                        if (cur_tab == pane.getSelectedComponent()) {
+                               System.out.printf("switch tabs\n");
                                pane.setSelectedComponent(tab);
                        }
                        cur_tab = tab;
                                pane.setSelectedComponent(tab);
                        }
                        cur_tab = tab;
index b0cff38176d3abedf6d2d33fc51447e4a6812a1c..a6b967f8e3eb5a23f9f4ecc26d2f882be6ba9227 100644 (file)
@@ -481,8 +481,7 @@ public class AltosUI extends AltosUIFrame {
                AltosRecordSet set = record_set(file);
                if (set == null)
                        return null;
                AltosRecordSet set = record_set(file);
                if (set == null)
                        return null;
-//             return new AltosReplayReader(states.iterator(), file);
-               return null;
+               return new AltosReplayReader(set, file);
        }
 
        static boolean process_replay(File file) {
        }
 
        static boolean process_replay(File file) {
index 52414c62038e155b2305a66559cff2cf84f3954f..1edac8a9bf38242fba8dc26b5c6d2cd1cda6c6ed 100644 (file)
@@ -30,7 +30,9 @@ public class AltosDisplayThread extends Thread {
        IdleThread              idle_thread;
        AltosVoice              voice;
        AltosFlightReader       reader;
        IdleThread              idle_thread;
        AltosVoice              voice;
        AltosFlightReader       reader;
-       AltosState              old_state, state;
+       AltosState              state;
+       int                     old_state = AltosLib.ao_flight_invalid;
+       boolean                 old_gps_ready = false;
        AltosListenerState      listener_state;
        AltosFlightDisplay      display;
 
        AltosListenerState      listener_state;
        AltosFlightDisplay      display;
 
@@ -164,7 +166,7 @@ public class AltosDisplayThread extends Thread {
                }
 
                public synchronized void notice(boolean spoken) {
                }
 
                public synchronized void notice(boolean spoken) {
-                       if (old_state != null && old_state.state() != state.state()) {
+                       if (old_state != state.state()) {
                                report_time = now();
                                this.notify();
                        } else if (spoken)
                                report_time = now();
                                this.notify();
                        } else if (spoken)
@@ -179,16 +181,16 @@ public class AltosDisplayThread extends Thread {
 
        synchronized boolean tell() {
                boolean ret = false;
 
        synchronized boolean tell() {
                boolean ret = false;
-               if (old_state == null || old_state.state() != state.state()) {
+               if (old_state != state.state()) {
                        if (state.state() != AltosLib.ao_flight_stateless)
                                voice.speak(state.state_name());
                        if (state.state() != AltosLib.ao_flight_stateless)
                                voice.speak(state.state_name());
-                       if ((old_state == null || old_state.state() <= AltosLib.ao_flight_boost) &&
+                       if ((old_state == AltosLib.ao_flight_invalid || old_state <= AltosLib.ao_flight_boost) &&
                            state.state() > AltosLib.ao_flight_boost) {
                                if (state.max_speed() != AltosLib.MISSING)
                                        voice.speak("max speed: %s.",
                                                    AltosConvert.speed.say_units(state.max_speed() + 0.5));
                                ret = true;
                            state.state() > AltosLib.ao_flight_boost) {
                                if (state.max_speed() != AltosLib.MISSING)
                                        voice.speak("max speed: %s.",
                                                    AltosConvert.speed.say_units(state.max_speed() + 0.5));
                                ret = true;
-                       } else if ((old_state == null || old_state.state() < AltosLib.ao_flight_drogue) &&
+                       } else if ((old_state == AltosLib.ao_flight_invalid || old_state < AltosLib.ao_flight_drogue) &&
                                   state.state() >= AltosLib.ao_flight_drogue) {
                                if (state.max_height() != AltosLib.MISSING)
                                        voice.speak("max height: %s.",
                                   state.state() >= AltosLib.ao_flight_drogue) {
                                if (state.max_height() != AltosLib.MISSING)
                                        voice.speak("max height: %s.",
@@ -196,17 +198,18 @@ public class AltosDisplayThread extends Thread {
                                ret = true;
                        }
                }
                                ret = true;
                        }
                }
-               if (old_state == null || old_state.gps_ready != state.gps_ready) {
+               if (old_gps_ready != state.gps_ready) {
                        if (state.gps_ready) {
                                voice.speak("GPS ready");
                                ret = true;
                        }
                        if (state.gps_ready) {
                                voice.speak("GPS ready");
                                ret = true;
                        }
-                       else if (old_state != null) {
+                       else if (old_gps_ready) {
                                voice.speak("GPS lost");
                                ret = true;
                        }
                }
                                voice.speak("GPS lost");
                                ret = true;
                        }
                }
-               old_state = state;
+               old_state = state.state();
+               old_gps_ready = state.gps_ready;
                return ret;
        }
 
                return ret;
        }
 
@@ -220,14 +223,11 @@ public class AltosDisplayThread extends Thread {
                try {
                        for (;;) {
                                try {
                try {
                        for (;;) {
                                try {
-                                       AltosState new_state = reader.read();
-                                       if (new_state == null) {
-                                               state = null;
+                                       state = reader.read();
+                                       if (state == null) {
                                                listener_state.running = false;
                                                break;
                                        }
                                                listener_state.running = false;
                                                break;
                                        }
-                                       reader.update(new_state);
-                                       state = new_state;
                                        show_safely();
                                        told = tell();
                                        idle_thread.notice(told);
                                        show_safely();
                                        told = tell();
                                        idle_thread.notice(told);