Add simple post-flight analysis tool (ao-postflight)
authorKeith Packard <keithp@keithp.com>
Sun, 6 Sep 2009 05:03:31 +0000 (22:03 -0700)
committerKeith Packard <keithp@keithp.com>
Sun, 6 Sep 2009 05:03:31 +0000 (22:03 -0700)
This tool reads either an eeprom or telem log file and displays some
rudimentary data (max accel/alt for each flight stage).

Signed-off-by: Keith Packard <keithp@keithp.com>
15 files changed:
.gitignore
ao-tools/Makefile.am
ao-tools/ao-dumplog/ao-dumplog.c
ao-tools/ao-postflight/Makefile.am [new file with mode: 0644]
ao-tools/ao-postflight/ao-postflight.1 [new file with mode: 0644]
ao-tools/ao-postflight/ao-postflight.c [new file with mode: 0644]
ao-tools/lib/Makefile.am
ao-tools/lib/cc-analyse.c [new file with mode: 0644]
ao-tools/lib/cc-convert.c [new file with mode: 0644]
ao-tools/lib/cc-log.c [new file with mode: 0644]
ao-tools/lib/cc-logfile.c [new file with mode: 0644]
ao-tools/lib/cc-telem.c [new file with mode: 0644]
ao-tools/lib/cc-util.c
ao-tools/lib/cc.h
configure.ac

index b3d2d56..0ca4bed 100644 (file)
@@ -22,9 +22,13 @@ ao-teleterra.h
 ao-tidongle.h
 ao-tools/ao-bitbang/ao-bitbang
 ao-tools/ao-dbg/ao-dbg
+ao-tools/ao-dumplog/ao-dumplog
 ao-tools/ao-eeprom/ao-eeprom
+ao-tools/ao-list/ao-list
 ao-tools/ao-load/ao-load
+ao-tools/ao-postflight/ao-postflight
 ao-tools/ao-rawload/ao-rawload
+ao-tools/ao-view/ao-view
 ao-view/Makefile
 ao-view/ao-view
 autom4te.cache
index b61f045..2850e90 100644 (file)
@@ -1 +1 @@
-SUBDIRS=lib ao-rawload ao-dbg ao-dumplog ao-bitbang ao-eeprom ao-list ao-load ao-view
+SUBDIRS=lib ao-rawload ao-dbg ao-dumplog ao-bitbang ao-eeprom ao-list ao-load ao-postflight ao-view
index 4bccfd6..b930f0e 100644 (file)
@@ -86,6 +86,7 @@ main (int argc, char **argv)
                        if (!out) {
                                perror(filename);
                        }
+                       fprintf (out, "%s\n", line);
                } else if (sscanf(line, "%c %x %x %x", &cmd, &tick, &a, &b) == 4) {
                        if (out) {
                                fprintf(out, "%s\n", line);
diff --git a/ao-tools/ao-postflight/Makefile.am b/ao-tools/ao-postflight/Makefile.am
new file mode 100644 (file)
index 0000000..301ac45
--- /dev/null
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-postflight
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS) $(GNOME_CFLAGS)
+AO_POSTFLIGHT_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+
+ao_postflight_DEPENDENCIES = $(AO_POSTFLIGHT_LIBS)
+
+ao_postflight_LDADD=$(AO_POSTFLIGHT_LIBS) $(LIBUSB_LIBS) $(GNOME_LIBS)
+
+ao_postflight_SOURCES = ao-postflight.c
+
+man_MANS = ao-postflight.1
diff --git a/ao-tools/ao-postflight/ao-postflight.1 b/ao-tools/ao-postflight/ao-postflight.1
new file mode 100644 (file)
index 0000000..fe02587
--- /dev/null
@@ -0,0 +1,29 @@
+.\"
+.\" Copyright © 2009 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; either version 2 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" 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.
+.\"
+.\"
+.TH AO-POSTFLIGHT 1 "ao-postflight" ""
+.SH NAME
+ao-postflight \- Analyse a flight log (either telemetry or eeprom)
+.SH SYNOPSIS
+.B "ao-postflight"
+{flight.eeprom|flight.telem}
+.SH DESCRIPTION
+.I ao-postflight
+reads the specified flight log and produces a summary of the flight on stdout.
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-postflight/ao-postflight.c b/ao-tools/ao-postflight/ao-postflight.c
new file mode 100644 (file)
index 0000000..f0e2c2a
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2009 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "cc-usb.h"
+#include "cc.h"
+
+#define NUM_BLOCK      512
+
+static const struct option options[] = {
+       { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+       fprintf(stderr, "usage: %s {flight-log} ...\n", program);
+       exit(1);
+}
+
+static const char *state_names[] = {
+       "startup",
+       "idle",
+       "pad",
+       "boost",
+       "fast",
+       "coast",
+       "drogue",
+       "main",
+       "landed",
+       "invalid"
+};
+
+void
+analyse_flight(struct cc_flightraw *f)
+{
+       double  height;
+       double  accel;
+       double  boost_start, boost_stop;
+       double  min_pres;
+       int     i;
+       int     pres_i, accel_i;
+       int     boost_start_set = 0;
+       int     boost_stop_set = 0;
+       enum ao_flight_state    state;
+       double  state_start, state_stop;
+
+       printf ("Flight:  %9d\nSerial:  %9d\n",
+               f->flight, f->serial);
+       boost_start = f->accel.data[0].time;
+       boost_stop = f->accel.data[f->accel.num-1].time;
+       for (i = 0; i < f->state.num; i++) {
+               if (f->state.data[i].value == ao_flight_boost && !boost_start_set) {
+                       boost_start = f->state.data[i].time;
+                       boost_start_set = 1;
+               }
+               if (f->state.data[i].value > ao_flight_boost && !boost_stop_set) {
+                       boost_stop = f->state.data[i].time;
+                       boost_stop_set = 1;
+               }
+       }
+
+       pres_i = cc_timedata_min(&f->pres, f->pres.data[0].time,
+                                f->pres.data[f->pres.num-1].time);
+       min_pres = f->pres.data[pres_i].value;
+       height = cc_barometer_to_altitude(min_pres) -
+               cc_barometer_to_altitude(f->ground_pres);
+       printf ("Max height: %9.2fm    %9.2fft %9.2fs\n",
+               height, height * 100 / 2.54 / 12,
+               (f->pres.data[pres_i].time - boost_start) / 100.0);
+
+       accel_i = cc_timedata_min(&f->accel, boost_start, boost_stop);
+       accel = cc_accelerometer_to_acceleration(f->accel.data[accel_i].value,
+                                                f->ground_accel);
+       printf ("Max accel:  %9.2fm/s² %9.2fg  %9.2fs\n",
+               accel, accel /  9.80665,
+               (f->accel.data[accel_i].time - boost_start) / 100.0);
+       for (i = 0; i < f->state.num; i++) {
+               state = f->state.data[i].value;
+               state_start = f->state.data[i].time;
+               if (i < f->state.num - 1)
+                       state_stop = f->state.data[i+1].time;
+               else
+                       state_stop = f->accel.data[f->accel.num-1].time;
+               printf("State: %s\n", state_names[state]);
+               printf("\tStart:      %9.2fs\n", (state_start - boost_start) / 100.0);
+               printf("\tDuration:   %9.2fs\n", (state_stop - state_start) / 100.0);
+               accel_i = cc_timedata_min(&f->accel, state_start, state_stop);
+               accel = cc_accelerometer_to_acceleration(f->accel.data[accel_i].value,
+                                                        f->ground_accel);
+               printf("\tMax accel:  %9.2fm/s² %9.2fg  %9.2fs\n",
+                      accel, accel / 9.80665,
+                      (f->accel.data[accel_i].time - boost_start) / 100.0);
+
+               pres_i = cc_timedata_min(&f->pres, state_start, state_stop);
+               min_pres = f->pres.data[pres_i].value;
+               height = cc_barometer_to_altitude(min_pres) -
+                       cc_barometer_to_altitude(f->ground_pres);
+               printf ("\tMax height: %9.2fm    %9.2fft %9.2fs\n",
+                       height, height * 100 / 2.54 / 12,
+                       (f->pres.data[pres_i].time - boost_start) / 100.0);
+       }
+}
+
+int
+main (int argc, char **argv)
+{
+       FILE                    *file;
+       int                     i;
+       int                     ret = 0;
+       struct cc_flightraw     *raw;
+       int                     c;
+       int                     serial;
+       char                    *s;
+
+       while ((c = getopt_long(argc, argv, "", options, NULL)) != -1) {
+               switch (c) {
+               default:
+                       usage(argv[0]);
+                       break;
+               }
+       }
+       for (i = optind; i < argc; i++) {
+               file = fopen(argv[i], "r");
+               if (!file) {
+                       perror(argv[i]);
+                       ret++;
+                       continue;
+               }
+               s = strstr(argv[i], "-serial-");
+               if (s)
+                       serial = atoi(s + 8);
+               else
+                       serial = 0;
+               raw = cc_log_read(file);
+               if (!raw) {
+                       perror(argv[i]);
+                       ret++;
+                       continue;
+               }
+               if (!raw->serial)
+                       raw->serial = serial;
+               analyse_flight(raw);
+               cc_flightraw_free(raw);
+       }
+       return ret;
+}
index da13ede..e682f75 100644 (file)
@@ -14,6 +14,8 @@ libao_tools_a_SOURCES = \
        ccdbg-memory.c \
        ccdbg-rom.c \
        ccdbg-state.c \
+       cc-analyse.c \
+       cc-convert.c \
        cc-log.c \
        cc-usb.c \
        cc-usb.h \
@@ -22,5 +24,7 @@ libao_tools_a_SOURCES = \
        cc-util.c \
        cc-bitbang.c \
        cc-bitbang.h \
+       cc-logfile.c \
+       cc-telem.c \
        cp-usb-async.c \
        cp-usb-async.h
diff --git a/ao-tools/lib/cc-analyse.c b/ao-tools/lib/cc-analyse.c
new file mode 100644 (file)
index 0000000..6fd36cd
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2009 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.
+ */
+
+#include "cc.h"
+
+int
+cc_timedata_min(struct cc_timedata *d, double min_time, double max_time)
+{
+       int     i;
+       int     set = 0;
+       int     min_i;
+       double  min;
+
+       if (d->num == 0)
+               return 0;
+       for (i = 0; i < d->num; i++)
+               if (min_time <= d->data[i].time && d->data[i].time <= max_time)
+                       if (!set || d->data[i].value < min) {
+                               min_i = i;
+                               min = d->data[i].value;
+                               set = 1;
+                       }
+       return min_i;
+}
+
+int
+cc_timedata_max(struct cc_timedata *d, double min_time, double max_time)
+{
+       int     i;
+       double  max;
+       int     max_i;
+       int     set = 0;
+
+       if (d->num == 0)
+               return 0;
+       for (i = 0; i < d->num; i++)
+               if (min_time <= d->data[i].time && d->data[i].time <= max_time)
+                       if (!set || d->data[i].value > max) {
+                               max_i = i;
+                               max = d->data[i].value;
+                               set = 1;
+                       }
+       return max_i;
+}
diff --git a/ao-tools/lib/cc-convert.c b/ao-tools/lib/cc-convert.c
new file mode 100644 (file)
index 0000000..ac6962b
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright © 2009 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.
+ */
+
+#include "cc.h"
+#include <math.h>
+
+/*
+ * Pressure Sensor Model, version 1.1
+ *
+ * written by Holly Grimes
+ *
+ * Uses the International Standard Atmosphere as described in
+ *   "A Quick Derivation relating altitude to air pressure" (version 1.03)
+ *    from the Portland State Aerospace Society, except that the atmosphere
+ *    is divided into layers with each layer having a different lapse rate.
+ *
+ * Lapse rate data for each layer was obtained from Wikipedia on Sept. 1, 2007
+ *    at site <http://en.wikipedia.org/wiki/International_Standard_Atmosphere
+ *
+ * Height measurements use the local tangent plane.  The postive z-direction is up.
+ *
+ * All measurements are given in SI units (Kelvin, Pascal, meter, meters/second^2).
+ *   The lapse rate is given in Kelvin/meter, the gas constant for air is given
+ *   in Joules/(kilogram-Kelvin).
+ */
+
+#define GRAVITATIONAL_ACCELERATION -9.80665
+#define AIR_GAS_CONSTANT       287.053
+#define NUMBER_OF_LAYERS       7
+#define MAXIMUM_ALTITUDE       84852.0
+#define MINIMUM_PRESSURE       0.3734
+#define LAYER0_BASE_TEMPERATURE        288.15
+#define LAYER0_BASE_PRESSURE   101325
+
+/* lapse rate and base altitude for each layer in the atmosphere */
+static const double lapse_rate[NUMBER_OF_LAYERS] = {
+       -0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002
+};
+
+static const int base_altitude[NUMBER_OF_LAYERS] = {
+       0, 11000, 20000, 32000, 47000, 51000, 71000
+};
+
+/* outputs atmospheric pressure associated with the given altitude. altitudes
+   are measured with respect to the mean sea level */
+double
+cc_altitude_to_pressure(double altitude)
+{
+
+   double base_temperature = LAYER0_BASE_TEMPERATURE;
+   double base_pressure = LAYER0_BASE_PRESSURE;
+
+   double pressure;
+   double base; /* base for function to determine pressure */
+   double exponent; /* exponent for function to determine pressure */
+   int layer_number; /* identifies layer in the atmosphere */
+   int delta_z; /* difference between two altitudes */
+
+   if (altitude > MAXIMUM_ALTITUDE) /* FIX ME: use sensor data to improve model */
+      return 0;
+
+   /* calculate the base temperature and pressure for the atmospheric layer
+      associated with the inputted altitude */
+   for(layer_number = 0; layer_number < NUMBER_OF_LAYERS - 1 && altitude > base_altitude[layer_number + 1]; layer_number++) {
+      delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
+      if (lapse_rate[layer_number] == 0.0) {
+         exponent = GRAVITATIONAL_ACCELERATION * delta_z
+              / AIR_GAS_CONSTANT / base_temperature;
+         base_pressure *= exp(exponent);
+      }
+      else {
+         base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+         exponent = GRAVITATIONAL_ACCELERATION /
+              (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+         base_pressure *= pow(base, exponent);
+      }
+      base_temperature += delta_z * lapse_rate[layer_number];
+   }
+
+   /* calculate the pressure at the inputted altitude */
+   delta_z = altitude - base_altitude[layer_number];
+   if (lapse_rate[layer_number] == 0.0) {
+      exponent = GRAVITATIONAL_ACCELERATION * delta_z
+           / AIR_GAS_CONSTANT / base_temperature;
+      pressure = base_pressure * exp(exponent);
+   }
+   else {
+      base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+      exponent = GRAVITATIONAL_ACCELERATION /
+           (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+      pressure = base_pressure * pow(base, exponent);
+   }
+
+   return pressure;
+}
+
+
+/* outputs the altitude associated with the given pressure. the altitude
+   returned is measured with respect to the mean sea level */
+double
+cc_pressure_to_altitude(double pressure)
+{
+
+   double next_base_temperature = LAYER0_BASE_TEMPERATURE;
+   double next_base_pressure = LAYER0_BASE_PRESSURE;
+
+   double altitude;
+   double base_pressure;
+   double base_temperature;
+   double base; /* base for function to determine base pressure of next layer */
+   double exponent; /* exponent for function to determine base pressure
+                             of next layer */
+   double coefficient;
+   int layer_number; /* identifies layer in the atmosphere */
+   int delta_z; /* difference between two altitudes */
+
+   if (pressure < 0)  /* illegal pressure */
+      return -1;
+   if (pressure < MINIMUM_PRESSURE) /* FIX ME: use sensor data to improve model */
+      return MAXIMUM_ALTITUDE;
+
+   /* calculate the base temperature and pressure for the atmospheric layer
+      associated with the inputted pressure. */
+   layer_number = -1;
+   do {
+      layer_number++;
+      base_pressure = next_base_pressure;
+      base_temperature = next_base_temperature;
+      delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
+      if (lapse_rate[layer_number] == 0.0) {
+         exponent = GRAVITATIONAL_ACCELERATION * delta_z
+              / AIR_GAS_CONSTANT / base_temperature;
+         next_base_pressure *= exp(exponent);
+      }
+      else {
+         base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
+         exponent = GRAVITATIONAL_ACCELERATION /
+              (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
+         next_base_pressure *= pow(base, exponent);
+      }
+      next_base_temperature += delta_z * lapse_rate[layer_number];
+   }
+   while(layer_number < NUMBER_OF_LAYERS - 1 && pressure < next_base_pressure);
+
+   /* calculate the altitude associated with the inputted pressure */
+   if (lapse_rate[layer_number] == 0.0) {
+      coefficient = (AIR_GAS_CONSTANT / GRAVITATIONAL_ACCELERATION)
+                                                    * base_temperature;
+      altitude = base_altitude[layer_number]
+                    + coefficient * log(pressure / base_pressure);
+   }
+   else {
+      base = pressure / base_pressure;
+      exponent = AIR_GAS_CONSTANT * lapse_rate[layer_number]
+                                       / GRAVITATIONAL_ACCELERATION;
+      coefficient = base_temperature / lapse_rate[layer_number];
+      altitude = base_altitude[layer_number]
+                      + coefficient * (pow(base, exponent) - 1);
+   }
+
+   return altitude;
+}
+
+/*
+ * Values for our MP3H6115A pressure sensor
+ *
+ * From the data sheet:
+ *
+ * Pressure range: 15-115 kPa
+ * Voltage at 115kPa: 2.82
+ * Output scale: 27mV/kPa
+ *
+ *
+ * 27 mV/kPa * 2047 / 3300 counts/mV = 16.75 counts/kPa
+ * 2.82V * 2047 / 3.3 counts/V = 1749 counts/115 kPa
+ */
+
+static const double counts_per_kPa = 27 * 2047 / 3300;
+static const double counts_at_101_3kPa = 1674.0;
+
+double
+cc_barometer_to_pressure(double count)
+{
+       return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0;
+}
+
+double
+cc_barometer_to_altitude(double baro)
+{
+       double Pa = cc_barometer_to_pressure(baro);
+       return cc_pressure_to_altitude(Pa);
+}
+
+static const double count_per_mss = 27.0;
+
+double
+cc_accelerometer_to_acceleration(double accel, double ground_accel)
+{
+       return (ground_accel - accel) / count_per_mss;
+}
+
+double
+cc_thermometer_to_temperature(double thermo)
+{
+       return ((thermo / 32767 * 3.3) - 0.5) / 0.01;
+}
+
+double
+cc_battery_to_voltage(double battery)
+{
+       return battery / 32767.0 * 5.0;
+}
+
+double
+cc_ignitor_to_voltage(double ignite)
+{
+       return ignite / 32767 * 15.0;
+}
+
+static inline double sqr(double a) { return a * a; }
+
+void
+cc_great_circle (double start_lat, double start_lon,
+                double end_lat, double end_lon,
+                double *dist, double *bearing)
+{
+       const double rad = M_PI / 180;
+       const double earth_radius = 6371.2 * 1000;      /* in meters */
+       double lat1 = rad * start_lat;
+       double lon1 = rad * -start_lon;
+       double lat2 = rad * end_lat;
+       double lon2 = rad * -end_lon;
+
+//     double d_lat = lat2 - lat1;
+       double d_lon = lon2 - lon1;
+
+       /* From http://en.wikipedia.org/wiki/Great-circle_distance */
+       double vdn = sqrt(sqr(cos(lat2) * sin(d_lon)) +
+                         sqr(cos(lat1) * sin(lat2) -
+                             sin(lat1) * cos(lat2) * cos(d_lon)));
+       double vdd = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(d_lon);
+       double d = atan2(vdn,vdd);
+       double course;
+
+       if (cos(lat1) < 1e-20) {
+               if (lat1 > 0)
+                       course = M_PI;
+               else
+                       course = -M_PI;
+       } else {
+               if (d < 1e-10)
+                       course = 0;
+               else
+                       course = acos((sin(lat2)-sin(lat1)*cos(d)) /
+                                     (sin(d)*cos(lat1)));
+               if (sin(lon2-lon1) > 0)
+                       course = 2 * M_PI-course;
+       }
+       *dist = d * earth_radius;
+       *bearing = course * 180/M_PI;
+}
diff --git a/ao-tools/lib/cc-log.c b/ao-tools/lib/cc-log.c
new file mode 100644 (file)
index 0000000..dd8177f
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2009 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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <gconf/gconf-client.h>
+#include "cc.h"
+
+static char *cc_file_dir;
+
+#define ALTOS_DIR_PATH "/apps/aoview/log_dir"
+#define DEFAULT_DIR    "AltOS"
+
+static void
+cc_file_save_conf(void)
+{
+       GConfClient     *gconf_client;
+
+       g_type_init();
+       gconf_client = gconf_client_get_default();
+       if (gconf_client)
+       {
+               gconf_client_set_string(gconf_client,
+                                       ALTOS_DIR_PATH,
+                                       cc_file_dir,
+                                       NULL);
+               g_object_unref(G_OBJECT(gconf_client));
+       }
+}
+
+static void
+cc_file_load_conf(void)
+{
+       char *file_dir;
+       GConfClient     *gconf_client;
+
+       g_type_init();
+       gconf_client = gconf_client_get_default();
+       if (gconf_client)
+       {
+               file_dir = gconf_client_get_string(gconf_client,
+                                                  ALTOS_DIR_PATH,
+                                                  NULL);
+               g_object_unref(G_OBJECT(gconf_client));
+               if (file_dir)
+                       cc_file_dir = strdup(file_dir);
+       }
+}
+
+void
+cc_set_log_dir(char *dir)
+{
+       cc_file_dir = strdup(dir);
+       cc_file_save_conf();
+}
+
+char *
+cc_get_log_dir(void)
+{
+       cc_file_load_conf();
+       if (!cc_file_dir) {
+               cc_file_dir = cc_fullname(getenv("HOME"), DEFAULT_DIR);
+               cc_file_save_conf();
+       }
+       return cc_file_dir;
+}
+
+char *
+cc_make_filename(int serial, char *ext)
+{
+       char            base[50];
+       struct tm       tm;
+       time_t          now;
+       char            *full;
+       int             r;
+       int             sequence;
+
+       now = time(NULL);
+       (void) localtime_r(&now, &tm);
+       cc_mkdir(cc_get_log_dir());
+       sequence = 0;
+       for (;;) {
+               snprintf(base, sizeof (base), "%04d-%02d-%02d-serial-%03d-flight-%03d.%s",
+                       tm.tm_year + 1900,
+                       tm.tm_mon + 1,
+                       tm.tm_mday,
+                       serial,
+                       sequence,
+                       ext);
+               full = cc_fullname(cc_get_log_dir(), base);
+               r = access(full, F_OK);
+               if (r < 0)
+                       return full;
+               free(full);
+               sequence++;
+       }
+
+}
diff --git a/ao-tools/lib/cc-logfile.c b/ao-tools/lib/cc-logfile.c
new file mode 100644 (file)
index 0000000..444ff08
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Copyright © 2009 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.
+ */
+
+#include "cc.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+static int
+timedata_add(struct cc_timedata *data, double time, double value)
+{
+       struct cc_timedataelt   *newdata;
+       int                     newsize;
+       if (data->size == data->num) {
+               if (data->size == 0)
+                       newdata = malloc((newsize = 256) * sizeof (struct cc_timedataelt));
+               else
+                       newdata = realloc (data->data, (newsize = data->size * 2)
+                                          * sizeof (struct cc_timedataelt));
+               if (!newdata)
+                       return 0;
+               data->size = newsize;
+               data->data = newdata;
+       }
+       if (data->num && data->data[data->num-1].time > time)
+               time += 65536;
+       data->data[data->num].time = time;
+       data->data[data->num].value = value;
+       data->num++;
+       return 1;
+}
+
+static void
+timedata_free(struct cc_timedata *data)
+{
+       if (data->data)
+               free(data->data);
+}
+
+static int
+gpsdata_add(struct cc_gpsdata *data, struct cc_gpselt *elt)
+{
+       struct cc_gpselt        *newdata;
+       int                     newsize;
+       if (data->size == data->num) {
+               if (data->size == 0)
+                       newdata = malloc((newsize = 256) * sizeof (struct cc_gpselt));
+               else
+                       newdata = realloc (data->data, (newsize = data->size * 2)
+                                          * sizeof (struct cc_gpselt));
+               if (!newdata)
+                       return 0;
+               data->size = newsize;
+               data->data = newdata;
+       }
+       data->data[data->num] = *elt;
+       data->num++;
+       return 1;
+}
+
+static void
+gpsdata_free(struct cc_gpsdata *data)
+{
+       if (data->data)
+               free(data->data);
+}
+
+#define AO_LOG_FLIGHT          'F'
+#define AO_LOG_SENSOR          'A'
+#define AO_LOG_TEMP_VOLT       'T'
+#define AO_LOG_DEPLOY          'D'
+#define AO_LOG_STATE           'S'
+#define AO_LOG_GPS_TIME                'G'
+#define AO_LOG_GPS_LAT         'N'
+#define AO_LOG_GPS_LON         'W'
+#define AO_LOG_GPS_ALT         'H'
+#define AO_LOG_GPS_SAT         'V'
+
+#define AO_LOG_POS_NONE                (~0UL)
+
+static int
+read_eeprom(const char *line, struct cc_flightraw *f, double *ground_pres, int *ground_pres_count)
+{
+       char    type;
+       int     tick;
+       int     a, b;
+       struct cc_gpselt        gps;
+       int     serial;
+
+       if (sscanf(line, "serial-number %u", &serial) == 1) {
+               f->serial = serial;
+               return 1;
+       }
+       if (sscanf(line, "%c %x %x %x", &type, &tick, &a, &b) != 4)
+               return 0;
+       switch (type) {
+       case AO_LOG_FLIGHT:
+               f->ground_accel = a;
+               f->ground_pres = 0;
+               f->flight = b;
+               *ground_pres = 0;
+               *ground_pres_count = 0;
+               break;
+       case AO_LOG_SENSOR:
+               timedata_add(&f->accel, tick, a);
+               timedata_add(&f->pres, tick, b);
+               if (*ground_pres_count < 20) {
+                       *ground_pres += b;
+                       (*ground_pres_count)++;
+                       if (*ground_pres_count >= 20)
+                               f->ground_pres = *ground_pres / *ground_pres_count;
+               }
+               break;
+       case AO_LOG_TEMP_VOLT:
+               timedata_add(&f->temp, tick, a);
+               timedata_add(&f->volt, tick, b);
+               break;
+       case AO_LOG_DEPLOY:
+               timedata_add(&f->drogue, tick, a);
+               timedata_add(&f->main, tick, b);
+               break;
+       case AO_LOG_STATE:
+               timedata_add(&f->state, tick, a);
+               break;
+       case AO_LOG_GPS_TIME:
+               gps.time = tick;
+               break;
+       case AO_LOG_GPS_LAT:
+               gps.lat = ((int32_t) (a + (b << 16))) / 10000000.0;
+               break;
+       case AO_LOG_GPS_LON:
+               gps.lon = ((int32_t) (a + (b << 16))) / 10000000.0;
+               break;
+       case AO_LOG_GPS_ALT:
+               gps.alt = ((int32_t) (a + (b << 16)));
+               gpsdata_add(&f->gps, &gps);
+               break;
+       case AO_LOG_GPS_SAT:
+               break;
+       default:
+               return 0;
+       }
+       return 1;
+}
+
+static int
+read_telem(const char *line, struct cc_flightraw *f)
+{
+       struct cc_telem         telem;
+       struct cc_gpselt        gps;
+       if (!cc_telem_parse(line, &telem))
+               return 0;
+       f->ground_accel = telem.ground_accel;
+       f->ground_pres = telem.ground_pres;
+       f->flight = 0;
+       timedata_add(&f->accel, telem.tick, telem.flight_accel);
+       timedata_add(&f->pres, telem.tick, telem.flight_pres);
+       timedata_add(&f->temp, telem.tick, telem.temp);
+       timedata_add(&f->volt, telem.tick, telem.batt);
+       timedata_add(&f->drogue, telem.tick, telem.drogue);
+       timedata_add(&f->main, telem.tick, telem.main);
+       if (telem.gps.gps_locked) {
+               gps.time = telem.tick;
+               gps.lat = telem.gps.lat;
+               gps.lon = telem.gps.lon;
+               gps.alt = telem.gps.alt;
+               gpsdata_add(&f->gps, &gps);
+       }
+       return 1;
+}
+
+struct cc_flightraw *
+cc_log_read(FILE *file)
+{
+       struct cc_flightraw     *f;
+       char                    line[8192];
+       double                  ground_pres;
+       int                     ground_pres_count;
+
+       f = calloc(1, sizeof (struct cc_flightraw));
+       if (!f)
+               return NULL;
+       while (fgets(line, sizeof (line), file)) {
+               if (read_eeprom(line, f, &ground_pres, &ground_pres_count))
+                       continue;
+               if (read_telem(line, f))
+                       continue;
+               fprintf (stderr, "invalid line: %s", line);
+       }
+       return f;
+}
+
+void
+cc_flightraw_free(struct cc_flightraw *raw)
+{
+       timedata_free(&raw->accel);
+       timedata_free(&raw->pres);
+       timedata_free(&raw->temp);
+       timedata_free(&raw->volt);
+       timedata_free(&raw->main);
+       timedata_free(&raw->drogue);
+       timedata_free(&raw->state);
+       gpsdata_free(&raw->gps);
+       free(raw);
+}
diff --git a/ao-tools/lib/cc-telem.c b/ao-tools/lib/cc-telem.c
new file mode 100644 (file)
index 0000000..a6ac031
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2009 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.
+ */
+
+#include "cc.h"
+#include <string.h>
+#include <stdlib.h>
+
+static void
+cc_parse_string(char *target, int len, char *source)
+{
+       strncpy(target, source, len-1);
+       target[len-1] = '\0';
+}
+
+static void
+cc_parse_int(int *target, char *source)
+{
+       *target = strtol(source, NULL, 0);
+}
+
+static void
+cc_parse_hex(int *target, char *source)
+{
+       *target = strtol(source, NULL, 16);
+}
+
+static void
+cc_parse_pos(double *target, char *source)
+{
+       int     deg;
+       double  min;
+       char    dir;
+       double  r;
+
+       if (sscanf(source, "%d°%lf'%c", &deg, &min, &dir) != 3) {
+               *target = 0;
+               return;
+       }
+       r = deg + min / 60.0;
+       if (dir == 'S' || dir == 'W')
+               r = -r;
+       *target = r;
+}
+
+#define PARSE_MAX_WORDS        512
+
+int
+cc_telem_parse(const char *input_line, struct cc_telem *telem)
+{
+       char *saveptr;
+       char *words[PARSE_MAX_WORDS];
+       int nword;
+       char line_buf[8192], *line;
+       int     tracking_pos;
+
+       /* avoid smashing our input parameter */
+       strncpy (line_buf, input_line, sizeof (line_buf)-1);
+       line_buf[sizeof(line_buf) - 1] = '\0';
+       line = line_buf;
+       for (nword = 0; nword < PARSE_MAX_WORDS; nword++) {
+               words[nword] = strtok_r(line, " \t\n", &saveptr);
+               line = NULL;
+               if (words[nword] == NULL)
+                       break;
+       }
+       if (nword < 36)
+               return FALSE;
+       if (strcmp(words[0], "CALL") != 0)
+               return FALSE;
+       cc_parse_string(telem->callsign, sizeof (telem->callsign), words[1]);
+       cc_parse_int(&telem->serial, words[3]);
+
+       cc_parse_int(&telem->rssi, words[5]);
+       cc_parse_string(telem->state, sizeof (telem->state), words[9]);
+       cc_parse_int(&telem->tick, words[10]);
+       cc_parse_int(&telem->accel, words[12]);
+       cc_parse_int(&telem->pres, words[14]);
+       cc_parse_int(&telem->temp, words[16]);
+       cc_parse_int(&telem->batt, words[18]);
+       cc_parse_int(&telem->drogue, words[20]);
+       cc_parse_int(&telem->main, words[22]);
+       cc_parse_int(&telem->flight_accel, words[24]);
+       cc_parse_int(&telem->ground_accel, words[26]);
+       cc_parse_int(&telem->flight_vel, words[28]);
+       cc_parse_int(&telem->flight_pres, words[30]);
+       cc_parse_int(&telem->ground_pres, words[32]);
+       cc_parse_int(&telem->gps.nsat, words[34]);
+       if (strcmp (words[36], "unlocked") == 0) {
+               telem->gps.gps_connected = 1;
+               telem->gps.gps_locked = 0;
+               telem->gps.gps_time.hour = telem->gps.gps_time.minute = telem->gps.gps_time.second = 0;
+               telem->gps.lat = telem->gps.lon = 0;
+               telem->gps.alt = 0;
+               tracking_pos = 37;
+       } else if (nword >= 40) {
+               telem->gps.gps_locked = 1;
+               telem->gps.gps_connected = 1;
+               sscanf(words[36], "%d:%d:%d", &telem->gps.gps_time.hour, &telem->gps.gps_time.minute, &telem->gps.gps_time.second);
+               cc_parse_pos(&telem->gps.lat, words[37]);
+               cc_parse_pos(&telem->gps.lon, words[38]);
+               sscanf(words[39], "%dm", &telem->gps.alt);
+               tracking_pos = 46;
+       } else {
+               telem->gps.gps_connected = 0;
+               telem->gps.gps_locked = 0;
+               telem->gps.gps_time.hour = telem->gps.gps_time.minute = telem->gps.gps_time.second = 0;
+               telem->gps.lat = telem->gps.lon = 0;
+               telem->gps.alt = 0;
+               tracking_pos = -1;
+       }
+       if (nword >= 46) {
+               telem->gps.gps_extended = 1;
+               sscanf(words[40], "%lfm/s", &telem->gps.ground_speed);
+               sscanf(words[41], "%d", &telem->gps.course);
+               sscanf(words[42], "%lfm/s", &telem->gps.climb_rate);
+               sscanf(words[43], "%lf", &telem->gps.hdop);
+               sscanf(words[44], "%d", &telem->gps.h_error);
+               sscanf(words[45], "%d", &telem->gps.v_error);
+       } else {
+               telem->gps.gps_extended = 0;
+               telem->gps.ground_speed = 0;
+               telem->gps.course = 0;
+               telem->gps.climb_rate = 0;
+               telem->gps.hdop = 0;
+               telem->gps.h_error = 0;
+               telem->gps.v_error = 0;
+       }
+       if (tracking_pos >= 0 && nword >= tracking_pos + 2 && strcmp(words[tracking_pos], "SAT") == 0) {
+               int     c, n, pos;
+               cc_parse_int(&n, words[tracking_pos + 1]);
+               pos = tracking_pos + 2;
+               if (nword >= pos + n * 3) {
+                       telem->gps_tracking.channels = n;
+                       for (c = 0; c < n; c++) {
+                               cc_parse_int(&telem->gps_tracking.sats[c].svid,
+                                                words[pos + 0]);
+                               cc_parse_hex(&telem->gps_tracking.sats[c].state,
+                                                words[pos + 1]);
+                               cc_parse_int(&telem->gps_tracking.sats[c].c_n0,
+                                                words[pos + 2]);
+                               pos += 3;
+                       }
+               } else {
+                       telem->gps_tracking.channels = 0;
+               }
+       } else {
+               telem->gps_tracking.channels = 0;
+       }
+       return TRUE;
+}
index 7104470..65488ee 100644 (file)
@@ -15,8 +15,8 @@
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  */
 
-#include "cc.h"
 #define _GNU_SOURCE
+#include "cc.h"
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
index f92a29f..3975cf1 100644 (file)
@@ -18,6 +18,8 @@
 #ifndef _CC_H_
 #define _CC_H_
 
+#include <stdio.h>
+
 char *
 cc_fullname (char *dir, char *file);
 
@@ -60,4 +62,209 @@ cc_get_log_dir(void);
 char *
 cc_make_filename(int serial, char *ext);
 
+/*
+ * For sequential data which are not evenly spaced
+ */
+
+struct cc_timedataelt {
+       double  time;
+       double  value;
+};
+
+struct cc_timedata {
+       int                     num;
+       int                     size;
+       struct cc_timedataelt   *data;
+};
+
+
+/*
+ * For GPS data
+ */
+
+struct cc_gpselt {
+       double          time;
+       double          lat;
+       double          lon;
+       double          alt;
+};
+
+struct cc_gpsdata {
+       int                     num;
+       int                     size;
+       double                  time_offset;
+       struct cc_gpselt        *data;
+};
+
+/*
+ * For sequential data which are evenly spaced
+ */
+struct cc_perioddata {
+       int             num;
+       double          start;
+       double          step;
+       double          *data;
+};
+
+enum ao_flight_state {
+       ao_flight_startup = 0,
+       ao_flight_idle = 1,
+       ao_flight_pad = 2,
+       ao_flight_boost = 3,
+       ao_flight_fast = 4,
+       ao_flight_coast = 5,
+       ao_flight_drogue = 6,
+       ao_flight_main = 7,
+       ao_flight_landed = 8,
+       ao_flight_invalid = 9
+};
+
+struct cc_flightraw {
+       int                     flight;
+       int                     serial;
+       double                  ground_accel;
+       double                  ground_pres;
+       struct cc_timedata      accel;
+       struct cc_timedata      pres;
+       struct cc_timedata      temp;
+       struct cc_timedata      volt;
+       struct cc_timedata      main;
+       struct cc_timedata      drogue;
+       struct cc_timedata      state;
+       struct cc_gpsdata       gps;
+};
+
+struct cc_flightraw *
+cc_log_read(FILE *file);
+
+void
+cc_flightraw_free(struct cc_flightraw *raw);
+
+struct cc_flightcooked {
+       struct cc_perioddata    accel_accel;
+       struct cc_perioddata    accel_speed;
+       struct cc_perioddata    accel_pos;
+       struct cc_perioddata    pres_pos;
+       struct cc_perioddata    pres_speed;
+       struct cc_perioddata    pres_accel;
+       struct cc_perioddata    gps_lat;
+       struct cc_perioddata    gps_lon;
+       struct cc_perioddata    gps_alt;
+       struct cc_timedata      state;
+};
+
+/*
+ * Telemetry data contents
+ */
+
+
+struct cc_gps_time {
+       int hour;
+       int minute;
+       int second;
+};
+
+struct cc_gps {
+       int     nsat;
+       int     gps_locked;
+       int     gps_connected;
+       struct cc_gps_time gps_time;
+       double  lat;            /* degrees (+N -S) */
+       double  lon;            /* degrees (+E -W) */
+       int     alt;            /* m */
+
+       int     gps_extended;   /* has extra data */
+       double  ground_speed;   /* m/s */
+       int     course;         /* degrees */
+       double  climb_rate;     /* m/s */
+       double  hdop;           /* unitless? */
+       int     h_error;        /* m */
+       int     v_error;        /* m */
+};
+
+#define SIRF_SAT_STATE_ACQUIRED                        (1 << 0)
+#define SIRF_SAT_STATE_CARRIER_PHASE_VALID     (1 << 1)
+#define SIRF_SAT_BIT_SYNC_COMPLETE             (1 << 2)
+#define SIRF_SAT_SUBFRAME_SYNC_COMPLETE                (1 << 3)
+#define SIRF_SAT_CARRIER_PULLIN_COMPLETE       (1 << 4)
+#define SIRF_SAT_CODE_LOCKED                   (1 << 5)
+#define SIRF_SAT_ACQUISITION_FAILED            (1 << 6)
+#define SIRF_SAT_EPHEMERIS_AVAILABLE           (1 << 7)
+
+struct cc_gps_sat {
+       int     svid;
+       int     state;
+       int     c_n0;
+};
+
+struct cc_gps_tracking {
+       int                     channels;
+       struct cc_gps_sat       sats[12];
+};
+
+struct cc_telem {
+       char    callsign[16];
+       int     serial;
+       int     rssi;
+       char    state[16];
+       int     tick;
+       int     accel;
+       int     pres;
+       int     temp;
+       int     batt;
+       int     drogue;
+       int     main;
+       int     flight_accel;
+       int     ground_accel;
+       int     flight_vel;
+       int     flight_pres;
+       int     ground_pres;
+       struct cc_gps   gps;
+       struct cc_gps_tracking  gps_tracking;
+};
+
+int
+cc_telem_parse(const char *input_line, struct cc_telem *telem);
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+/* Conversion functions */
+double
+cc_pressure_to_altitude(double pressure);
+
+double
+cc_altitude_to_pressure(double altitude);
+
+double
+cc_barometer_to_pressure(double baro);
+
+double
+cc_barometer_to_altitude(double baro);
+
+double
+cc_accelerometer_to_acceleration(double accel, double ground_accel);
+
+double
+cc_thermometer_to_temperature(double thermo);
+
+double
+cc_battery_to_voltage(double battery);
+
+double
+cc_ignitor_to_voltage(double ignite);
+
+void
+cc_great_circle (double start_lat, double start_lon,
+                double end_lat, double end_lon,
+                double *dist, double *bearing);
+
+int
+cc_timedata_min(struct cc_timedata *d, double min_time, double max_time);
+
+int
+cc_timedata_max(struct cc_timedata *d, double min_time, double max_time);
+
 #endif /* _CC_H_ */
index 73a33ac..c668df0 100644 (file)
@@ -82,6 +82,7 @@ ao-tools/ao-bitbang/Makefile
 ao-tools/ao-eeprom/Makefile
 ao-tools/ao-list/Makefile
 ao-tools/ao-load/Makefile
+ao-tools/ao-postflight/Makefile
 ao-tools/ao-view/Makefile
 ao-utils/Makefile
 ])