From 80cadf44f5f1accd6ddfca25c2af8d4d424f26d9 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Thu, 9 Jul 2009 20:55:10 -0700 Subject: [PATCH] Show speed. Format numbers. Timeout and report final status. 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 --- aoview/aoview.glade | 35 +++++- aoview/aoview.h | 23 +++- aoview/aoview_label.c | 26 +++-- aoview/aoview_monitor.c | 93 +++++++-------- aoview/aoview_replay.c | 2 +- aoview/aoview_state.c | 252 +++++++++++++++++++++++++++------------- 6 files changed, 280 insertions(+), 151 deletions(-) diff --git a/aoview/aoview.glade b/aoview/aoview.glade index d828a85e..a2dc830f 100644 --- a/aoview/aoview.glade +++ b/aoview/aoview.glade @@ -3,7 +3,7 @@ - 300 + 550 700 True AltOS View @@ -288,13 +288,13 @@ True 2 - 3 + 4 3 True True - Height + Height (m) center @@ -311,7 +311,7 @@ True - RSSI + RSSI (dBm) 2 @@ -322,7 +322,7 @@ True 2 - 0m + 0 True @@ -348,7 +348,7 @@ True 2 - -50dBm + -50 True @@ -358,6 +358,29 @@ 2 + + + True + Speed (m/s) + + + 3 + 4 + + + + + True + 0 + True + + + 3 + 4 + 1 + 2 + + False diff --git a/aoview/aoview.h b/aoview/aoview.h index 5c118a11..ac64833f 100644 --- a/aoview/aoview.h +++ b/aoview/aoview.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -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); diff --git a/aoview/aoview_label.c b/aoview/aoview_label.c index 88b747ab..24313626 100644 --- a/aoview/aoview_label.c +++ b/aoview/aoview_label.c @@ -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 diff --git a/aoview/aoview_monitor.c b/aoview/aoview_monitor.c index 5810be5b..43381800 100644 --- a/aoview/aoview_monitor.c +++ b/aoview/aoview_monitor.c @@ -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, diff --git a/aoview/aoview_replay.c b/aoview/aoview_replay.c index 3eadb442..da7b5d6a 100644 --- a/aoview/aoview_replay.c +++ b/aoview/aoview_replay.c @@ -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)); diff --git a/aoview/aoview_state.c b/aoview/aoview_state.c index 030db99f..d5e978b6 100644 --- a/aoview/aoview_state.c +++ b/aoview/aoview_state.c @@ -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 -- 2.30.2