Show speed. Format numbers. Timeout and report final status.
authorKeith Packard <keithp@keithp.com>
Fri, 10 Jul 2009 03:55:10 +0000 (20:55 -0700)
committerKeith Packard <keithp@keithp.com>
Fri, 10 Jul 2009 03:55:10 +0000 (20:55 -0700)
The speed value is now shown in the top label bar. Ascent shows
accelerometer-derived data, otherwise it's baro derived.

All of the numbers displayed are now given sensible printf formats so they
don't contain way too many digits.

Instead of doing periodic reporting based on flight tick count, data is
reported every 10 seconds based on wall time. After landing, or when no data
have been received for a while, final flight information is spoken.

Signed-off-by: Keith Packard <keithp@keithp.com>
aoview/aoview.glade
aoview/aoview.h
aoview/aoview_label.c
aoview/aoview_monitor.c
aoview/aoview_replay.c
aoview/aoview_state.c

index d828a85e21f3f097a16b949a220eeac0c8e07d8d..a2dc830f00bae6f8f6c9796b658b093ba3c8b538 100644 (file)
@@ -3,7 +3,7 @@
   <!-- interface-requires gtk+ 2.16 -->
   <!-- interface-naming-policy project-wide -->
   <widget class="GtkWindow" id="aoview">
-    <property name="width_request">300</property>
+    <property name="width_request">550</property>
     <property name="height_request">700</property>
     <property name="visible">True</property>
     <property name="title" translatable="yes">AltOS View</property>
           <widget class="GtkTable" id="table1">
             <property name="visible">True</property>
             <property name="n_rows">2</property>
-            <property name="n_columns">3</property>
+            <property name="n_columns">4</property>
             <property name="row_spacing">3</property>
             <property name="homogeneous">True</property>
             <child>
               <widget class="GtkLabel" id="height_label">
                 <property name="visible">True</property>
-                <property name="label" translatable="yes">Height</property>
+                <property name="label" translatable="yes">Height (m)</property>
                 <property name="justify">center</property>
               </widget>
             </child>
             <child>
               <widget class="GtkLabel" id="rssi_label">
                 <property name="visible">True</property>
-                <property name="label" translatable="yes">RSSI</property>
+                <property name="label" translatable="yes">RSSI (dBm)</property>
               </widget>
               <packing>
                 <property name="left_attach">2</property>
               <widget class="GtkLabel" id="height_value">
                 <property name="visible">True</property>
                 <property name="ypad">2</property>
-                <property name="label" translatable="yes">0m</property>
+                <property name="label" translatable="yes">0</property>
                 <property name="selectable">True</property>
               </widget>
               <packing>
               <widget class="GtkLabel" id="rssi_value">
                 <property name="visible">True</property>
                 <property name="ypad">2</property>
-                <property name="label" translatable="yes">-50dBm</property>
+                <property name="label" translatable="yes">-50</property>
                 <property name="selectable">True</property>
               </widget>
               <packing>
                 <property name="bottom_attach">2</property>
               </packing>
             </child>
+            <child>
+              <widget class="GtkLabel" id="speed_label">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Speed (m/s)</property>
+              </widget>
+              <packing>
+                <property name="left_attach">3</property>
+                <property name="right_attach">4</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="speed_value">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">0</property>
+                <property name="selectable">True</property>
+              </widget>
+              <packing>
+                <property name="left_attach">3</property>
+                <property name="right_attach">4</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+              </packing>
+            </child>
           </widget>
           <packing>
             <property name="expand">False</property>
index 5c118a111d64d8f2a3eb59c4ce13c12d702ee565..ac64833fb3e301d9ab1f27ec461da39166b207ca 100644 (file)
@@ -35,6 +35,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <assert.h>
+#include <math.h>
 
 #include <gtk/gtk.h>
 #include <glade/glade.h>
@@ -50,7 +51,7 @@ struct usbdev {
        int     idVendor;
 };
 
-struct aostate {
+struct aodata {
        char    callsign[16];
        int     serial;
        int     rssi;
@@ -83,9 +84,17 @@ struct aostate {
        double  hdop;           /* unitless? */
        int     h_error;        /* m */
        int     v_error;        /* m */
+};
+
+struct aostate {
+       struct aodata   data;
 
        /* derived data */
 
+       struct aodata   prev_data;
+
+       double          report_time;
+
        gboolean        ascent; /* going up? */
 
        int     ground_altitude;
@@ -96,11 +105,16 @@ struct aostate {
        double  temperature;
        double  main_sense;
        double  drogue_sense;
+       double  baro_speed;
 
        int     max_height;
        double  max_acceleration;
        double  max_speed;
 
+       double  lat;
+       double  lon;
+       int     gps_valid;
+
        double  pad_lat;
        double  pad_lon;
        double  pad_alt;
@@ -112,8 +126,13 @@ struct aostate {
        double  distance;
        double  bearing;
        int     gps_height;
+
+       int     speak_tick;
+       int     speak_altitude;
 };
 
+extern struct aostate aostate;
+
 /* GPS is 'stable' when we've seen at least this many samples */
 #define MIN_PAD_SAMPLES        10
 
@@ -162,7 +181,7 @@ void
 aoview_usbdev_free(struct usbdev *usbdev);
 
 void
-aoview_state_notify(struct aostate *state);
+aoview_state_notify(struct aodata *data);
 
 void
 aoview_state_new(void);
index 88b747abb52fd52b2e1731625da39811e44f000a..24313626528fec18fb7df01c88c8dfb0cbfcec6a 100644 (file)
@@ -22,12 +22,14 @@ static struct {
        char            *initial_value;
        GtkLabel        *widget;
 } label_widgets[] = {
-       { "height_label", "Height", NULL },
+       { "height_label", "Height (m)", NULL },
        { "state_label", "State", NULL },
-       { "rssi_label", "RSSI", NULL },
-       { "height_value", "0m", NULL },
+       { "rssi_label", "RSSI (dBm)", NULL },
+       { "speed_label", "Speed (m/s)", NULL },
+       { "height_value", "0", NULL },
        { "state_value", "pad", NULL },
-       { "rssi_value", "-50dBm", NULL },
+       { "rssi_value", "-50", NULL },
+       { "speed_value", "0", NULL },
 };
 
 static void
@@ -44,13 +46,19 @@ void
 aoview_label_show(struct aostate *state)
 {
        char    line[1024];
-       sprintf(line, "%dm", state->height);
-       aoview_label_assign(label_widgets[3].widget, line);
+       sprintf(line, "%d", state->height);
+       aoview_label_assign(label_widgets[4].widget, line);
 
-       aoview_label_assign(label_widgets[4].widget, state->state);
+       aoview_label_assign(label_widgets[5].widget, state->data.state);
 
-       sprintf(line, "%ddBm", state->rssi);
-       aoview_label_assign(label_widgets[5].widget, line);
+       sprintf(line, "%d", state->data.rssi);
+       aoview_label_assign(label_widgets[6].widget, line);
+
+       if (state->ascent)
+               sprintf(line, "%6.0f", fabs(state->speed));
+       else
+               sprintf(line, "%6.0f", fabs(state->baro_speed));
+       aoview_label_assign(label_widgets[7].widget, line);
 }
 
 void
index 5810be5b91fb006117ac57f048421685a1a86930..43381800ac597ac531f615a3e885b89276d4957e 100644 (file)
@@ -65,8 +65,6 @@ aoview_parse_pos(double *target, char *source)
        *target = r;
 }
 
-static struct aostate  state;
-
 gboolean
 aoview_monitor_parse(const char *input_line)
 {
@@ -74,6 +72,7 @@ aoview_monitor_parse(const char *input_line)
        char *words[64];
        int nword;
        char line_buf[8192], *line;
+       struct aodata   data;
 
        /* avoid smashing our input parameter */
        strncpy (line_buf, input_line, sizeof (line_buf)-1);
@@ -89,61 +88,55 @@ aoview_monitor_parse(const char *input_line)
                return FALSE;
        if (strcmp(words[0], "CALL") != 0)
                return FALSE;
-       aoview_parse_string(state.callsign, sizeof (state.callsign), words[1]);
-       aoview_parse_int(&state.serial, words[3]);
-
-       aoview_parse_int(&state.rssi, words[5]);
-       aoview_parse_string(state.state, sizeof (state.state), words[9]);
-       aoview_parse_int(&state.tick, words[10]);
-       aoview_parse_int(&state.accel, words[12]);
-       aoview_parse_int(&state.pres, words[14]);
-       aoview_parse_int(&state.temp, words[16]);
-       aoview_parse_int(&state.batt, words[18]);
-       aoview_parse_int(&state.drogue, words[20]);
-       aoview_parse_int(&state.main, words[22]);
-       aoview_parse_int(&state.flight_accel, words[24]);
-       aoview_parse_int(&state.ground_accel, words[26]);
-       aoview_parse_int(&state.flight_vel, words[28]);
-       aoview_parse_int(&state.flight_pres, words[30]);
-       aoview_parse_int(&state.ground_pres, words[32]);
-       aoview_parse_int(&state.nsat, words[34]);
+       aoview_parse_string(data.callsign, sizeof (data.callsign), words[1]);
+       aoview_parse_int(&data.serial, words[3]);
+
+       aoview_parse_int(&data.rssi, words[5]);
+       aoview_parse_string(data.state, sizeof (data.state), words[9]);
+       aoview_parse_int(&data.tick, words[10]);
+       aoview_parse_int(&data.accel, words[12]);
+       aoview_parse_int(&data.pres, words[14]);
+       aoview_parse_int(&data.temp, words[16]);
+       aoview_parse_int(&data.batt, words[18]);
+       aoview_parse_int(&data.drogue, words[20]);
+       aoview_parse_int(&data.main, words[22]);
+       aoview_parse_int(&data.flight_accel, words[24]);
+       aoview_parse_int(&data.ground_accel, words[26]);
+       aoview_parse_int(&data.flight_vel, words[28]);
+       aoview_parse_int(&data.flight_pres, words[30]);
+       aoview_parse_int(&data.ground_pres, words[32]);
+       aoview_parse_int(&data.nsat, words[34]);
        if (strcmp (words[36], "unlocked") != 0 && nword >= 40) {
-               state.locked = 1;
-               sscanf(words[36], "%d:%d:%d", &state.gps_time.hour, &state.gps_time.minute, &state.gps_time.second);
-               aoview_parse_pos(&state.lat, words[37]);
-               aoview_parse_pos(&state.lon, words[38]);
-               sscanf(words[39], "%dm", &state.alt);
+               data.locked = 1;
+               sscanf(words[36], "%d:%d:%d", &data.gps_time.hour, &data.gps_time.minute, &data.gps_time.second);
+               aoview_parse_pos(&data.lat, words[37]);
+               aoview_parse_pos(&data.lon, words[38]);
+               sscanf(words[39], "%dm", &data.alt);
        } else {
-               state.locked = 0;
-               state.gps_time.hour = state.gps_time.minute = state.gps_time.second = 0;
-               state.lat = state.lon = 0;
-               state.alt = 0;
+               data.locked = 0;
+               data.gps_time.hour = data.gps_time.minute = data.gps_time.second = 0;
+               data.lat = data.lon = 0;
+               data.alt = 0;
        }
        if (nword >= 46) {
-               sscanf(words[40], "%lfm/s", &state.ground_speed);
-               sscanf(words[41], "%d", &state.course);
-               sscanf(words[42], "%lfm/s", &state.climb_rate);
-               sscanf(words[43], "%lf", &state.hdop);
-               sscanf(words[44], "%d", &state.h_error);
-               sscanf(words[45], "%d", &state.v_error);
+               sscanf(words[40], "%lfm/s", &data.ground_speed);
+               sscanf(words[41], "%d", &data.course);
+               sscanf(words[42], "%lfm/s", &data.climb_rate);
+               sscanf(words[43], "%lf", &data.hdop);
+               sscanf(words[44], "%d", &data.h_error);
+               sscanf(words[45], "%d", &data.v_error);
        } else {
-               state.ground_speed = 0;
-               state.course = 0;
-               state.climb_rate = 0;
-               state.hdop = 0;
-               state.h_error = 0;
-               state.v_error = 0;
+               data.ground_speed = 0;
+               data.course = 0;
+               data.climb_rate = 0;
+               data.hdop = 0;
+               data.h_error = 0;
+               data.v_error = 0;
        }
-       aoview_state_notify(&state);
+       aoview_state_notify(&data);
        return TRUE;
 }
 
-void
-aoview_monitor_reset(void)
-{
-       memset(&state, '\0', sizeof (state));
-}
-
 static void
 aoview_monitor_callback(gpointer user_data,
                        struct aoview_serial *serial,
@@ -166,7 +159,7 @@ aoview_monitor_callback(gpointer user_data,
                                monitor_line[monitor_pos] = '\0';
                                if (monitor_pos) {
                                        if (aoview_monitor_parse(monitor_line)) {
-                                               aoview_log_set_serial(state.serial);
+                                               aoview_log_set_serial(aostate.data.serial);
                                                if (aoview_log_get_serial())
                                                        aoview_log_printf ("%s\n", monitor_line);
                                        }
@@ -186,7 +179,7 @@ aoview_monitor_connect(char *tty)
        if (!monitor_serial)
                return FALSE;
        aoview_table_clear();
-       aoview_monitor_reset();
+       aoview_state_reset();
        aoview_serial_set_callback(monitor_serial,
                                   aoview_monitor_callback,
                                   monitor_serial,
index 3eadb442a463cdbfd0ba49e1e5c2b67941162224..da7b5d6a6cce83a439fe43737752ef4203b03fe5 100644 (file)
@@ -107,7 +107,7 @@ aoview_replay_open(GtkWidget *widget, gpointer data)
                gtk_widget_destroy(dialog);
        } else {
                replay_tick = -1;
-               aoview_monitor_reset();
+               aoview_state_reset();
                aoview_replay_read(NULL);
        }
        gtk_widget_hide(GTK_WIDGET(replay_dialog));
index 030db99ffca94eb8fd4a5db237425e5b3c13f2f6..d5e978b68833a856651fd0a999f9c7c9506d4997 100644 (file)
@@ -86,28 +86,54 @@ static char *ascent_states[] = {
        0,
 };
 
+static double
+aoview_time(void)
+{
+       struct timespec now;
+
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       return (double) now.tv_sec + (double) now.tv_nsec / 1.0e9;
+}
+
 /*
  * Fill out the derived data fields
  */
 static void
-aoview_state_derive(struct aostate *state)
+aoview_state_derive(struct aodata *data, struct aostate *state)
 {
        int     i;
+       double  new_height;
+       double  height_change;
+       double  time_change;
+       int     tick_count;
 
-       state->ground_altitude = aoview_pres_to_altitude(state->ground_pres);
-       state->height = aoview_pres_to_altitude(state->flight_pres) - state->ground_altitude;
-       state->acceleration = (state->ground_accel - state->flight_accel) / 27.0;
-       state->speed = state->flight_vel / 2700.0;
-       state->temperature = ((state->temp / 32767.0 * 3.3) - 0.5) / 0.01;
-       state->drogue_sense = state->drogue / 32767.0 * 15.0;
-       state->main_sense = state->main / 32767.0 * 15.0;
-       state->battery = state->batt / 32767.0 * 5.0;
-       if (!strcmp(state->state, "pad")) {
-               if (state->locked && state->nsat > 4) {
+       state->report_time = aoview_time();
+
+       state->prev_data = state->data;
+       state->data = *data;
+       tick_count = data->tick;
+       if (tick_count < state->prev_data.tick)
+               tick_count += 65536;
+       time_change = (tick_count - state->prev_data.tick) / 100.0;
+
+       state->ground_altitude = aoview_pres_to_altitude(data->ground_pres);
+       new_height = aoview_pres_to_altitude(data->flight_pres) - state->ground_altitude;
+       height_change = new_height - state->height;
+       state->height = new_height;
+       if (time_change)
+               state->baro_speed = (state->baro_speed * 3 + (height_change / time_change)) / 4.0;
+       state->acceleration = (data->ground_accel - data->flight_accel) / 27.0;
+       state->speed = data->flight_vel / 2700.0;
+       state->temperature = ((data->temp / 32767.0 * 3.3) - 0.5) / 0.01;
+       state->drogue_sense = data->drogue / 32767.0 * 15.0;
+       state->main_sense = data->main / 32767.0 * 15.0;
+       state->battery = data->batt / 32767.0 * 5.0;
+       if (!strcmp(data->state, "pad")) {
+               if (data->locked && data->nsat > 4) {
                        state->npad++;
-                       state->pad_lat_total += state->lat;
-                       state->pad_lon_total += state->lon;
-                       state->pad_alt_total += state->alt;
+                       state->pad_lat_total += data->lat;
+                       state->pad_lon_total += data->lon;
+                       state->pad_alt_total += data->alt;
                        state->pad_lat = state->pad_lat_total / state->npad;
                        state->pad_lon = state->pad_lon_total / state->npad;
                        state->pad_alt = state->pad_alt_total / state->npad;
@@ -115,7 +141,7 @@ aoview_state_derive(struct aostate *state)
        }
        state->ascent = FALSE;
        for (i = 0; ascent_states[i]; i++)
-               if (!strcmp(state->state, ascent_states[i]))
+               if (!strcmp(data->state, ascent_states[i]))
                        state->ascent = TRUE;
 
        /* Only look at accelerometer data on the way up */
@@ -126,59 +152,117 @@ aoview_state_derive(struct aostate *state)
 
        if (state->height > state->max_height)
                state->max_height = state->height;
-       aoview_great_circle(state->pad_lat, state->pad_lon, state->lat, state->lon,
-                           &state->distance, &state->bearing);
+       if (data->locked) {
+               state->lat = data->lat;
+               state->lon = data->lon;
+               aoview_great_circle(state->pad_lat, state->pad_lon, data->lat, data->lon,
+                                   &state->distance, &state->bearing);
+               state->gps_valid = 1;
+       }
        if (state->npad) {
-               state->gps_height = state->alt - state->pad_alt;
+               state->gps_height = data->alt - state->pad_alt;
        } else {
                state->gps_height = 0;
        }
 }
 
 void
-aoview_state_speak(struct aostate *state)
+aoview_speak_state(struct aostate *state)
 {
-       static char     last_state[32];
-       int             i;
-       gboolean        report = FALSE;
-       int             this_tick;
-       static int      last_tick;
-       static int      last_altitude;
-       int             this_altitude;
-
-       if (strcmp(state->state, last_state)) {
-               aoview_voice_speak("%s\n", state->state);
-               if (!strcmp(state->state, "drogue"))
+       if (strcmp(state->data.state, state->prev_data.state)) {
+               aoview_voice_speak("%s\n", state->data.state);
+               if (!strcmp(state->data.state, "drogue"))
                        aoview_voice_speak("apogee %d meters\n",
                                           (int) state->max_height);
-               report = TRUE;
-               strcpy(last_state, state->state);
-       }
-       this_altitude = aoview_pres_to_altitude(state->flight_pres) - aoview_pres_to_altitude(state->ground_pres);
-       this_tick = state->tick;
-       while (this_tick < last_tick)
-               this_tick += 65536;
-       if (strcmp(state->state, "pad") != 0) {
-               if (this_altitude / 1000 != last_altitude / 1000)
-                       report = TRUE;
-               if (this_tick - last_tick >= 10 * 100)
-                       report = TRUE;
+               if (!strcmp(state->prev_data.state, "boost"))
+                       aoview_voice_speak("max speed %d meters per second\n",
+                                          (int) state->max_speed);
        }
-       if (report) {
-               aoview_voice_speak("%d meters\n",
-                                  this_altitude);
-               if (state->ascent)
-                       aoview_voice_speak("%d meters per second\n",
-                                          state->flight_vel / 2700);
-               last_tick = state->tick;
-               last_altitude = this_altitude;
+}
+
+void
+aoview_speak_height(struct aostate *state)
+{
+       aoview_voice_speak("%d meters\n", state->height);
+}
+
+struct aostate aostate;
+
+static guint aostate_timeout;
+
+#define COMPASS_LIMIT(n)       ((n * 22.5) + 22.5/2)
+
+static char *compass_points[] = {
+       "north",
+       "north north east",
+       "north east",
+       "east north east",
+       "east",
+       "east south east",
+       "south east",
+       "south south east",
+       "south",
+       "south south west",
+       "south west",
+       "west south west",
+       "west",
+       "west north west",
+       "north west",
+       "north north west",
+};
+
+static char *
+aoview_compass_point(double bearing)
+{
+       int     i;
+       while (bearing < 0)
+               bearing += 360.0;
+       while (bearing >= 360.0)
+               bearing -= 360.0;
+
+       i = floor ((bearing - 22.5/2) / 22.5 + 0.5);
+       if (i < 0) i = 0;
+       if (i >= sizeof (compass_points) / sizeof (compass_points[0]))
+               i = 0;
+       return compass_points[i];
+}
+
+static gboolean
+aoview_state_timeout(gpointer data)
+{
+       double  now = aoview_time();
+
+       if (strlen(aostate.data.state) > 0 && strcmp(aostate.data.state, "pad") != 0)
+               aoview_speak_height(&aostate);
+       if (now - aostate.report_time >= 20 || !strcmp(aostate.data.state, "landed")) {
+               if (!aostate.ascent) {
+                       if (fabs(aostate.baro_speed) < 20 && aostate.height < 100)
+                               aoview_voice_speak("rocket landed safely\n");
+                       else
+                               aoview_voice_speak("rocket may have crashed\n");
+                       if (aostate.gps_valid) {
+                               aoview_voice_speak("rocket reported %s of pad distance %d meters\n",
+                                                  aoview_compass_point(aostate.bearing),
+                                                  (int) aostate.distance);
+                       }
+               }
+               aostate_timeout = 0;
+               return FALSE;
        }
+       return TRUE;
+}
+
+void
+aoview_state_reset(void)
+{
+       memset(&aostate, '\0', sizeof (aostate));
 }
 
 void
-aoview_state_notify(struct aostate *state)
+aoview_state_notify(struct aodata *data)
 {
-       aoview_state_derive(state);
+       struct aostate *state = &aostate;
+       aoview_state_derive(data, state);
        aoview_table_start();
 
        if (state->npad >= MIN_PAD_SAMPLES)
@@ -186,40 +270,40 @@ aoview_state_notify(struct aostate *state)
        else
                aoview_table_add_row("Ground state", "waiting for gps (%d)",
                                     MIN_PAD_SAMPLES - state->npad);
-       aoview_table_add_row("Rocket state", "%s", state->state);
-       aoview_table_add_row("Callsign", "%s", state->callsign);
-       aoview_table_add_row("Rocket serial", "%d", state->serial);
-
-       aoview_table_add_row("RSSI", "%ddBm", state->rssi);
-       aoview_table_add_row("Height", "%dm", state->height);
-       aoview_table_add_row("Max height", "%dm", state->max_height);
-       aoview_table_add_row("Acceleration", "%gm/s²", state->acceleration);
-       aoview_table_add_row("Max acceleration", "%gm/s²", state->max_acceleration);
-       aoview_table_add_row("Speed", "%gm/s", state->speed);
-       aoview_table_add_row("Max Speed", "%gm/s", state->max_speed);
-       aoview_table_add_row("Temperature", "%g°C", state->temperature);
-       aoview_table_add_row("Battery", "%gV", state->battery);
-       aoview_table_add_row("Drogue", "%gV", state->drogue_sense);
-       aoview_table_add_row("Main", "%gV", state->main_sense);
+       aoview_table_add_row("Rocket state", "%s", state->data.state);
+       aoview_table_add_row("Callsign", "%s", state->data.callsign);
+       aoview_table_add_row("Rocket serial", "%d", state->data.serial);
+
+       aoview_table_add_row("RSSI", "%6ddBm", state->data.rssi);
+       aoview_table_add_row("Height", "%6dm", state->height);
+       aoview_table_add_row("Max height", "%6dm", state->max_height);
+       aoview_table_add_row("Acceleration", "%7.1fm/s²", state->acceleration);
+       aoview_table_add_row("Max acceleration", "%7.1fm/s²", state->max_acceleration);
+       aoview_table_add_row("Speed", "%7.1fm/s", state->ascent ? state->speed : state->baro_speed);
+       aoview_table_add_row("Max Speed", "%7.1fm/s", state->max_speed);
+       aoview_table_add_row("Temperature", "%6.2f°C", state->temperature);
+       aoview_table_add_row("Battery", "%5.2fV", state->battery);
+       aoview_table_add_row("Drogue", "%5.2fV", state->drogue_sense);
+       aoview_table_add_row("Main", "%5.2fV", state->main_sense);
        aoview_table_add_row("Pad altitude", "%dm", state->ground_altitude);
-       aoview_table_add_row("Satellites", "%d", state->nsat);
-       if (state->locked) {
-               aoview_state_add_deg("Latitude", state->lat, 'N', 'S');
-               aoview_state_add_deg("Longitude", state->lon, 'E', 'W');
+       aoview_table_add_row("Satellites", "%d", state->data.nsat);
+       if (state->data.locked) {
+               aoview_state_add_deg("Latitude", state->data.lat, 'N', 'S');
+               aoview_state_add_deg("Longitude", state->data.lon, 'E', 'W');
                aoview_table_add_row("GPS height", "%d", state->gps_height);
                aoview_table_add_row("GPS time", "%02d:%02d:%02d",
-                                    state->gps_time.hour,
-                                    state->gps_time.minute,
-                                    state->gps_time.second);
-               aoview_table_add_row("GPS ground speed", "%fm/s %d°",
-                                    state->ground_speed,
-                                    state->course);
-               aoview_table_add_row("GPS climb rate", "%fm/s",
-                                    state->climb_rate);
+                                    state->data.gps_time.hour,
+                                    state->data.gps_time.minute,
+                                    state->data.gps_time.second);
+               aoview_table_add_row("GPS ground speed", "%7.1fm/s %d°",
+                                    state->data.ground_speed,
+                                    state->data.course);
+               aoview_table_add_row("GPS climb rate", "%7.1fm/s",
+                                    state->data.climb_rate);
                aoview_table_add_row("GPS precision", "%f(hdop) %dm(h) %dm(v)\n",
-                                    state->hdop, state->h_error, state->v_error);
-               aoview_table_add_row("Distance from pad", "%gm", state->distance);
-               aoview_table_add_row("Direction from pad", "%g°", state->bearing);
+                                    state->data.hdop, state->data.h_error, state->data.v_error);
+               aoview_table_add_row("Distance from pad", "%5.0fm", state->distance);
+               aoview_table_add_row("Direction from pad", "%4.0f°", state->bearing);
        } else {
                aoview_table_add_row("GPS", "unlocked");
        }
@@ -230,7 +314,9 @@ aoview_state_notify(struct aostate *state)
        }
        aoview_table_finish();
        aoview_label_show(state);
-       aoview_state_speak(state);
+       aoview_speak_state(state);
+       if (!aostate_timeout && strcmp(state->data.state, "pad") != 0)
+               aostate_timeout = g_timeout_add_seconds(10, aoview_state_timeout, NULL);
 }
 
 void