summaryrefslogtreecommitdiff
path: root/ao-tools
diff options
context:
space:
mode:
Diffstat (limited to 'ao-tools')
-rw-r--r--ao-tools/Makefile.am2
-rw-r--r--ao-tools/ao-dbg/ao-dbg-main.c5
-rw-r--r--ao-tools/ao-dbg/ao-dbg-parse.c5
-rw-r--r--ao-tools/ao-dbg/ao-dbg.120
-rw-r--r--ao-tools/ao-dbg/ao-dbg.h2
-rw-r--r--ao-tools/ao-dumplog/Makefile.am12
-rw-r--r--ao-tools/ao-dumplog/ao-dumplog.174
-rw-r--r--ao-tools/ao-dumplog/ao-dumplog.c217
-rw-r--r--ao-tools/ao-eeprom/ao-eeprom.126
-rw-r--r--ao-tools/ao-eeprom/ao-eeprom.c20
-rw-r--r--ao-tools/ao-list/Makefile.am12
-rw-r--r--ao-tools/ao-list/ao-list.132
-rw-r--r--ao-tools/ao-list/ao-list.c41
-rw-r--r--ao-tools/ao-load/ao-load.137
-rw-r--r--ao-tools/ao-load/ao-load.c122
-rw-r--r--ao-tools/ao-postflight/Makefile.am12
-rw-r--r--ao-tools/ao-postflight/ao-postflight.161
-rw-r--r--ao-tools/ao-postflight/ao-postflight.c827
-rw-r--r--ao-tools/ao-rawload/ao-rawload.135
-rw-r--r--ao-tools/ao-rawload/ao-rawload.c25
-rw-r--r--ao-tools/ao-send-telem/Makefile.am12
-rw-r--r--ao-tools/ao-send-telem/ao-send-telem.164
-rw-r--r--ao-tools/ao-send-telem/ao-send-telem.c241
-rw-r--r--ao-tools/ao-stmload/.gitignore1
-rw-r--r--ao-tools/ao-stmload/Makefile.am15
-rw-r--r--ao-tools/ao-stmload/ao-stmload.161
-rw-r--r--ao-tools/ao-stmload/ao-stmload.c598
-rw-r--r--ao-tools/ao-telem/.gitignore1
-rw-r--r--ao-tools/ao-telem/Makefile.am12
-rw-r--r--ao-tools/ao-telem/ao-telem.161
-rw-r--r--ao-tools/ao-telem/ao-telem.c201
-rw-r--r--ao-tools/ao-view/.gitignore4
-rw-r--r--ao-tools/ao-view/Makefile.am38
-rw-r--r--ao-tools/ao-view/ao-view.150
-rw-r--r--ao-tools/ao-view/aoview.glade845
-rw-r--r--ao-tools/ao-view/aoview.h269
-rw-r--r--ao-tools/ao-view/aoview_channel.c90
-rw-r--r--ao-tools/ao-view/aoview_convert.c66
-rw-r--r--ao-tools/ao-view/aoview_dev_dialog.c173
-rw-r--r--ao-tools/ao-view/aoview_eeprom.c159
-rw-r--r--ao-tools/ao-view/aoview_file.c235
-rw-r--r--ao-tools/ao-view/aoview_flite.c141
-rw-r--r--ao-tools/ao-view/aoview_label.c73
-rw-r--r--ao-tools/ao-view/aoview_log.c82
-rw-r--r--ao-tools/ao-view/aoview_main.c113
-rw-r--r--ao-tools/ao-view/aoview_monitor.c108
-rw-r--r--ao-tools/ao-view/aoview_replay.c147
-rw-r--r--ao-tools/ao-view/aoview_serial.c270
-rw-r--r--ao-tools/ao-view/aoview_state.c371
-rw-r--r--ao-tools/ao-view/aoview_table.c83
-rw-r--r--ao-tools/ao-view/aoview_util.c91
-rw-r--r--ao-tools/ao-view/aoview_voice.c122
-rw-r--r--ao-tools/ao-view/design27
-rw-r--r--ao-tools/lib/Makefile.am24
-rw-r--r--ao-tools/lib/cc-analyse.c267
-rw-r--r--ao-tools/lib/cc-convert.c284
-rw-r--r--ao-tools/lib/cc-dsp.c145
-rw-r--r--ao-tools/lib/cc-integrate.c81
-rw-r--r--ao-tools/lib/cc-log.c121
-rw-r--r--ao-tools/lib/cc-logfile.c336
-rw-r--r--ao-tools/lib/cc-period.c56
-rw-r--r--ao-tools/lib/cc-process.c166
-rw-r--r--ao-tools/lib/cc-telem.c212
-rw-r--r--ao-tools/lib/cc-telemetry.c62
-rw-r--r--ao-tools/lib/cc-telemetry.h243
-rw-r--r--ao-tools/lib/cc-usb.c195
-rw-r--r--ao-tools/lib/cc-usb.h12
-rw-r--r--ao-tools/lib/cc-usbdev.c317
-rw-r--r--ao-tools/lib/cc-util.c80
-rw-r--r--ao-tools/lib/cc.h373
-rw-r--r--ao-tools/lib/cephes.h122
-rw-r--r--ao-tools/lib/chbevl.c81
-rw-r--r--ao-tools/lib/cmath.h179
-rw-r--r--ao-tools/lib/i0.c414
-rw-r--r--ao-tools/lib/mconf.h211
-rw-r--r--ao-tools/tests/reset6
76 files changed, 10276 insertions, 122 deletions
diff --git a/ao-tools/Makefile.am b/ao-tools/Makefile.am
index 98b88f38..257fdaec 100644
--- a/ao-tools/Makefile.am
+++ b/ao-tools/Makefile.am
@@ -1 +1 @@
-SUBDIRS=lib ao-rawload ao-dbg ao-bitbang ao-eeprom ao-load
+SUBDIRS=lib ao-rawload ao-dbg ao-bitbang ao-eeprom ao-list ao-load ao-telem ao-stmload ao-send-telem
diff --git a/ao-tools/ao-dbg/ao-dbg-main.c b/ao-tools/ao-dbg/ao-dbg-main.c
index f1e2c111..21b83a3d 100644
--- a/ao-tools/ao-dbg/ao-dbg-main.c
+++ b/ao-tools/ao-dbg/ao-dbg-main.c
@@ -34,6 +34,7 @@ struct ccdbg *s51_dbg;
int s51_interrupted = 0;
int s51_monitor = 0;
char *s51_tty = NULL;
+char *s51_device = NULL;
static FILE *s51_input;
static FILE *s51_output;
@@ -52,6 +53,7 @@ void s51_sigint()
static const struct option options[] = {
{ .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
{ 0, 0, 0, 0 },
};
@@ -114,6 +116,9 @@ main(int argc, char **argv)
case 'T':
s51_tty = optarg;
break;
+ case 'D':
+ s51_device = optarg;
+ break;
}
}
if (s51_port) {
diff --git a/ao-tools/ao-dbg/ao-dbg-parse.c b/ao-tools/ao-dbg/ao-dbg-parse.c
index 825d0e9c..dcb9099d 100644
--- a/ao-tools/ao-dbg/ao-dbg-parse.c
+++ b/ao-tools/ao-dbg/ao-dbg-parse.c
@@ -195,6 +195,11 @@ command_read (void)
enum command_result result;
struct command_function *func;
+ if (!s51_tty) {
+ if (!s51_device)
+ s51_device = getenv("AO_DBG_DEVICE");
+ s51_tty = cc_usbdevs_find_by_arg(s51_device, "TIDongle");
+ }
s51_dbg = ccdbg_open (s51_tty);
if (!s51_dbg)
exit(1);
diff --git a/ao-tools/ao-dbg/ao-dbg.1 b/ao-tools/ao-dbg/ao-dbg.1
index a850c454..00d3ac86 100644
--- a/ao-tools/ao-dbg/ao-dbg.1
+++ b/ao-tools/ao-dbg/ao-dbg.1
@@ -35,6 +35,9 @@ ao-dbg \- hex debugger for cc1111 processors
[\-h]
[\-m]
[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
.SH DESCRIPTION
.I ao-dbg
connects to a cc1111 processor through either a suitable cc1111 board
@@ -80,11 +83,26 @@ This should print a usage message, but does nothing useful currently.
.IP "\-m"
This option is not present in the original 8051 emulator, and causes ao-dbg to
dump all commands and replies that are received from and sent to sdcdb.
-.IP "\-T"
+.TP
+\-T tty-device | --tty tty-device
This selects which tty device the debugger uses to communicate with
the target device. The special name 'BITBANG' directs ao-dbg to use
the cp2103 connection, otherwise this should be a usb serial port
connected to a suitable cc1111 debug node.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleMetrum:2
+.br
+TeleMetrum
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
.SH COMMANDS
Once started, ao-dbg connects to the cc1111 and then reads and
executes commands, either from stdin, or the nework connection to
diff --git a/ao-tools/ao-dbg/ao-dbg.h b/ao-tools/ao-dbg/ao-dbg.h
index c1789d10..edc650a5 100644
--- a/ao-tools/ao-dbg/ao-dbg.h
+++ b/ao-tools/ao-dbg/ao-dbg.h
@@ -17,12 +17,14 @@
*/
#include <ccdbg.h>
+#include <cc.h>
extern char *s51_prompt;
extern struct ccdbg *s51_dbg;
extern int s51_interrupted;
extern int s51_monitor;
extern char *s51_tty;
+extern char *s51_device;
enum command_result {
command_success, command_debug, command_syntax, command_interrupt, command_error,
diff --git a/ao-tools/ao-dumplog/Makefile.am b/ao-tools/ao-dumplog/Makefile.am
new file mode 100644
index 00000000..a80cac33
--- /dev/null
+++ b/ao-tools/ao-dumplog/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-dumplog
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS) $(GNOME_CFLAGS)
+AO_DUMPLOG_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+
+ao_dumplog_DEPENDENCIES = $(AO_DUMPLOG_LIBS)
+
+ao_dumplog_LDADD=$(AO_DUMPLOG_LIBS) $(LIBUSB_LIBS) $(GNOME_LIBS)
+
+ao_dumplog_SOURCES = ao-dumplog.c
+
+man_MANS = ao-dumplog.1
diff --git a/ao-tools/ao-dumplog/ao-dumplog.1 b/ao-tools/ao-dumplog/ao-dumplog.1
new file mode 100644
index 00000000..d381e335
--- /dev/null
+++ b/ao-tools/ao-dumplog/ao-dumplog.1
@@ -0,0 +1,74 @@
+.\"
+.\" 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-DUMPLOG 1 "ao-dumplog" ""
+.SH NAME
+ao-dumplog \- Store flight log from TeleMetrum device
+.SH SYNOPSIS
+.B "ao-dumplog"
+[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
+[\--R\fP]
+[\--remote\fP]
+.SH OPTIONS
+.TP
+\-T tty-device | --tty tty-device
+This selects which tty device ao-dumplog uses to communicate with
+the target device.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleMetrum:2
+.br
+TeleMetrum
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
+.TP
+\-R | --remote
+This uses the command radio link to download the log from TeleMetrum
+through a TeleDongle.
+.SH DESCRIPTION
+.I ao-dumplog
+downloads the flight log from a connected TeleMetrum device and stores
+it to the configured flight log directory using a name of the form
+.IP
+\fIyyyy\fP-\fImm\fP-\fIdd\fP-serialP-\fIsss\fP-flight-\fIfff\fP.eeprom
+.PP
+\fIyyyy\fP is the current year
+.br
+\fImm\fP is the current month
+.br
+\fIdd\fP is the current day
+.br
+\fIsss\fP is the device serial number
+.br
+\fIfff\fP is a flight sequence number (to make filenames unique)
+.SH USAGE
+.I ao-dumplog
+connects to the specified target device and dumps the stored flight
+log.
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-dumplog/ao-dumplog.c b/ao-tools/ao-dumplog/ao-dumplog.c
new file mode 100644
index 00000000..6d4fa5bf
--- /dev/null
+++ b/ao-tools/ao-dumplog/ao-dumplog.c
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include "cc-usb.h"
+#include "cc.h"
+
+#define NUM_BLOCK 512
+
+static const struct option options[] = {
+ { .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
+ { .name = "remote", .has_arg = 0, .val = 'R' },
+ { .name = "channel", .has_arg = 1, .val = 'C' },
+ { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+ fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>] [--remote] [--channel <radio-channel>]\n", program);
+ exit(1);
+}
+
+static uint8_t
+log_checksum(int d[8])
+{
+ uint8_t sum = 0x5a;
+ int i;
+
+ for (i = 0; i < 8; i++)
+ sum += (uint8_t) d[i];
+ return -sum;
+}
+
+static const char *state_names[] = {
+ "startup",
+ "idle",
+ "pad",
+ "boost",
+ "fast",
+ "coast",
+ "drogue",
+ "main",
+ "landed",
+ "invalid"
+};
+
+
+int
+main (int argc, char **argv)
+{
+ struct cc_usb *cc;
+ char *tty = NULL;
+ char *device = NULL;
+ int c;
+ char line[8192];
+ FILE *out;
+ char *filename;
+ int serial_number = 0;
+ int channel = 0;
+ int flight = 0;
+ char cmd;
+ int tick, a, b;
+ int block;
+ int addr;
+ int received_addr;
+ int data[8];
+ int done;
+ int column;
+ int remote = 0;
+ int any_valid;
+ int invalid;
+ char serial_line[8192];
+
+ while ((c = getopt_long(argc, argv, "T:D:C:R", options, NULL)) != -1) {
+ switch (c) {
+ case 'T':
+ tty = optarg;
+ break;
+ case 'D':
+ device = optarg;
+ break;
+ case 'R':
+ remote = 1;
+ break;
+ case 'C':
+ channel = atoi(optarg);
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ if (!tty) {
+ if (remote)
+ tty = cc_usbdevs_find_by_arg(device, "TeleDongle");
+ else
+ tty = cc_usbdevs_find_by_arg(device, "TeleMetrum");
+ }
+ if (!tty)
+ tty = getenv("ALTOS_TTY");
+ if (!tty)
+ tty="/dev/ttyACM0";
+ cc = cc_usb_open(tty);
+ if (!cc)
+ exit(1);
+ if (remote)
+ cc_usb_open_remote(cc, channel);
+ /* send a 'version' command followed by a 'log' command */
+ cc_usb_printf(cc, "v\n");
+ out = NULL;
+ for (;;) {
+ cc_usb_getline(cc, line, sizeof (line));
+ if (sscanf(line, "serial-number %u", &serial_number) == 1)
+ strcpy(serial_line, line);
+ if (!strncmp(line, "software-version", 16))
+ break;
+ }
+ if (!serial_number) {
+ fprintf(stderr, "no serial number found\n");
+ cc_usb_close(cc);
+ exit(1);
+ }
+ printf ("Serial number: %d\n", serial_number);
+ done = 0;
+ column = 0;
+ for (block = 0; !done && block < 511; block++) {
+ cc_usb_printf(cc, "e %x\n", block);
+ if (column == 64) {
+ putchar('\n');
+ column = 0;
+ }
+ putchar('.'); fflush(stdout); column++;
+ any_valid = 0;
+ for (addr = 0; addr < 0x100;) {
+ cc_usb_getline(cc, line, sizeof (line));
+ if (sscanf(line, "00%x %x %x %x %x %x %x %x %x",
+ &received_addr,
+ &data[0], &data[1], &data[2], &data[3],
+ &data[4], &data[5], &data[6], &data[7]) == 9)
+ {
+ if (received_addr != addr)
+ fprintf(stderr, "data out of sync at 0x%x\n",
+ block * 256 + received_addr);
+
+ if (log_checksum(data) != 0)
+ fprintf (stderr, "invalid checksum at 0x%x\n",
+ block * 256 + received_addr);
+ else
+ any_valid = 1;
+
+ cmd = data[0];
+ tick = data[2] + (data[3] << 8);
+ a = data[4] + (data[5] << 8);
+ b = data[6] + (data[7] << 8);
+ if (cmd == 'F') {
+ flight = b;
+ filename = cc_make_filename(serial_number, flight, "eeprom");
+ printf ("Flight: %d\n", flight);
+ printf ("File name: %s\n", filename);
+ out = fopen (filename, "w");
+ if (!out) {
+ perror(filename);
+ exit(1);
+ }
+ fprintf(out, "%s\n", serial_line);
+ }
+
+ if (cmd == 'S' && a <= 8) {
+ if (column) putchar('\n');
+ printf("%s\n", state_names[a]);
+ column = 0;
+ }
+ if (out) {
+ fprintf(out, "%c %4x %4x %4x\n",
+ cmd, tick, a, b);
+ if (cmd == 'S' && a == 8) {
+ fclose(out);
+ out = NULL;
+ done = 1;
+ }
+ }
+ addr += 8;
+ }
+ }
+ if (!any_valid) {
+ fclose(out);
+ out = NULL;
+ done = 1;
+ }
+ }
+ if (column)
+ putchar('\n');
+ if (out)
+ fclose (out);
+ cc_usb_close(cc);
+ exit (0);
+}
diff --git a/ao-tools/ao-eeprom/ao-eeprom.1 b/ao-tools/ao-eeprom/ao-eeprom.1
index 8caff9d1..ed498147 100644
--- a/ao-tools/ao-eeprom/ao-eeprom.1
+++ b/ao-tools/ao-eeprom/ao-eeprom.1
@@ -21,7 +21,31 @@
ao-eeprom \- Fetch eeprom contents from TeleMetrum device
.SH SYNOPSIS
.B "ao-eeprom"
-[\-tty \fItty-device\fP]
+[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
+.SH OPTIONS
+.TP
+\-T tty-device | --tty tty-device
+This selects which tty device the debugger uses to communicate with
+the target device. The special name 'BITBANG' directs ao-dbg to use
+the cp2103 connection, otherwise this should be a usb serial port
+connected to a suitable cc1111 debug node.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleMetrum:2
+.br
+TeleMetrum
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
.SH DESCRIPTION
.I ao-eeprom
downloads the eeprom contents from a connected TeleMetrum device.
diff --git a/ao-tools/ao-eeprom/ao-eeprom.c b/ao-tools/ao-eeprom/ao-eeprom.c
index 726cc22c..b865e298 100644
--- a/ao-tools/ao-eeprom/ao-eeprom.c
+++ b/ao-tools/ao-eeprom/ao-eeprom.c
@@ -21,17 +21,19 @@
#include <unistd.h>
#include <getopt.h>
#include "cc-usb.h"
+#include "cc.h"
#define NUM_BLOCK 512
static const struct option options[] = {
{ .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
{ 0, 0, 0, 0},
};
static void usage(char *program)
{
- fprintf(stderr, "usage: %s [--tty <tty-name>]\n", program);
+ fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>\n", program);
exit(1);
}
@@ -40,35 +42,41 @@ main (int argc, char **argv)
{
struct cc_usb *cc;
int block;
- uint8_t bytes[32 * (2 + 8)];
+ uint8_t bytes[2 * 32 * (2 + 8)];
uint8_t *b;
int i, j;
uint32_t addr;
char *tty = NULL;
+ char *device = NULL;
int c;
- while ((c = getopt_long(argc, argv, "T:", options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "T:D:", options, NULL)) != -1) {
switch (c) {
case 'T':
tty = optarg;
break;
+ case 'D':
+ device = optarg;
+ break;
default:
usage(argv[0]);
break;
}
}
if (!tty)
+ tty = cc_usbdevs_find_by_arg(device, "TeleMetrum");
+ if (!tty)
tty = getenv("ALTOS_TTY");
if (!tty)
tty="/dev/ttyACM0";
cc = cc_usb_open(tty);
if (!cc)
exit(1);
- for (block = 0; block < NUM_BLOCK; block++) {
+ for (block = 0; block < NUM_BLOCK; block += 2) {
cc_queue_read(cc, bytes, sizeof (bytes));
- cc_usb_printf(cc, "e %x\n", block);
+ cc_usb_printf(cc, "e %x\ne %x\n", block, block + 1);
cc_usb_sync(cc);
- for (i = 0; i < 32; i++) {
+ for (i = 0; i < 32 * 2; i++) {
b = bytes + (i * 10);
addr = block * 256 + i * 8;
printf ("%06x", addr);
diff --git a/ao-tools/ao-list/Makefile.am b/ao-tools/ao-list/Makefile.am
new file mode 100644
index 00000000..de3c4deb
--- /dev/null
+++ b/ao-tools/ao-list/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-list
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS)
+AO_LIST_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+
+ao_list_DEPENDENCIES = $(AO_LIST_LIBS)
+
+ao_list_LDADD=$(AO_LIST_LIBS) $(LIBUSB_LIBS)
+
+ao_list_SOURCES = ao-list.c
+
+man_MANS = ao-list.1
diff --git a/ao-tools/ao-list/ao-list.1 b/ao-tools/ao-list/ao-list.1
new file mode 100644
index 00000000..03968c25
--- /dev/null
+++ b/ao-tools/ao-list/ao-list.1
@@ -0,0 +1,32 @@
+/.\"
+.\" 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-LIST 1 "ao-list" ""
+.SH NAME
+ao-list \- List connected AltOS devices
+.SH SYNOPSIS
+.B "ao-list"
+.SH DESCRIPTION
+.I ao-list
+scans the attached USB devices, locates those running AltOS and
+displays their product name and serial number along with the tty
+device associated with the serial port over USB provided by AltOS.
+.SH USAGE
+.I ao-list
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-list/ao-list.c b/ao-tools/ao-list/ao-list.c
new file mode 100644
index 00000000..c4b43d8f
--- /dev/null
+++ b/ao-tools/ao-list/ao-list.c
@@ -0,0 +1,41 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "cc.h"
+
+int
+main (int argc, char **argv)
+{
+ struct cc_usbdevs *devs;
+ struct cc_usbdev *dev;
+ int i;
+
+ devs = cc_usbdevs_scan();
+ if (devs) {
+ for (i = 0; i < devs->ndev; i++) {
+ dev = devs->dev[i];
+ printf ("%-20.20s %6d %s\n",
+ dev->product, dev->serial, dev->tty);
+ }
+ cc_usbdevs_free(devs);
+ }
+ return 0;
+}
diff --git a/ao-tools/ao-load/ao-load.1 b/ao-tools/ao-load/ao-load.1
index 10484f3b..79b76a79 100644
--- a/ao-tools/ao-load/ao-load.1
+++ b/ao-tools/ao-load/ao-load.1
@@ -21,13 +21,48 @@
ao-load \- flash a program to a AltOS device
.SH SYNOPSIS
.B "ao-load"
-[\-tty \fItty-device\fP]
+[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
+[\--cal \fIradio-calibration\fP]
\fIfile.ihx\fP
\fIdevice serial number\fP
.SH DESCRIPTION
.I ao-load
loads the specified .ihx file into the target device flash memory,
customizing the AltOS image with the specified serial number.
+.SH OPTIONS
+.TP
+\-T tty-device | --tty tty-device
+This selects which tty device the debugger uses to communicate with
+the target device. The special name 'BITBANG' directs ao-dbg to use
+the cp2103 connection, otherwise this should be a usb serial port
+connected to a suitable cc1111 debug node.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleMetrum:2
+.br
+TeleMetrum
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
+.TP
+\-c radio-calibration | --cal radio-calibration
+This programs the radio calibration value into the image for hardware
+which doesn't have any eeprom storage for this value. The value here
+can be computed given the current radio calibration value, the
+measured frequency and the desired frequency:
+.IP
+ cal' = cal * (desired/measured)
+.IP
+The default calibration value is 1186611.
.SH USAGE
.I ao-load
reads the specified .ihx file into memory, locates the matching .map
diff --git a/ao-tools/ao-load/ao-load.c b/ao-tools/ao-load/ao-load.c
index c27fcbe9..e3cef4a5 100644
--- a/ao-tools/ao-load/ao-load.c
+++ b/ao-tools/ao-load/ao-load.c
@@ -22,20 +22,25 @@
#include <unistd.h>
#include <getopt.h>
#include "ccdbg.h"
+#include "cc.h"
#define AO_USB_DESC_STRING 3
struct sym {
unsigned addr;
char *name;
-} serial_symbols[] = {
- { 0, "_ao_serial_number" },
-#define AO_SERIAL_NUMBER (serial_symbols[0].addr)
- { 0, "_ao_usb_descriptors" },
-#define AO_USB_DESCRIPTORS (serial_symbols[1].addr)
+ int required;
+} ao_symbols[] = {
+ { 0, "_ao_serial_number", 1 },
+#define AO_SERIAL_NUMBER (ao_symbols[0].addr)
+ { 0, "_ao_usb_descriptors", 0 },
+#define AO_USB_DESCRIPTORS (ao_symbols[1].addr)
+ { 0, "_ao_radio_cal", 1 },
+#define AO_RADIO_CAL (ao_symbols[2].addr)
};
-#define NUM_SERIAL_SYMBOLS (sizeof(serial_symbols)/sizeof(serial_symbols[0]))
+#define NUM_SYMBOLS 3
+#define NUM_REQUIRED_SYMBOLS 2
static int
find_symbols(FILE *map)
@@ -47,7 +52,7 @@ find_symbols(FILE *map)
char *colon;
unsigned long a;
int s;
- int found = 0;
+ int required = 0;
while (fgets(line, sizeof(line), map) != NULL) {
line[sizeof(line)-1] = '\0';
@@ -63,14 +68,15 @@ find_symbols(FILE *map)
a = strtoul(colon+1, &addr_end, 16);
if (a == ULONG_MAX || addr_end == addr)
continue;
- for (s = 0; s < NUM_SERIAL_SYMBOLS; s++)
- if (!strcmp(serial_symbols[s].name, name)) {
- serial_symbols[s].addr = (unsigned) a;
- ++found;
+ for (s = 0; s < NUM_SYMBOLS; s++)
+ if (!strcmp(ao_symbols[s].name, name)) {
+ ao_symbols[s].addr = (unsigned) a;
+ if (ao_symbols[s].required)
+ ++required;
break;
}
}
- return found == NUM_SERIAL_SYMBOLS;
+ return required >= NUM_REQUIRED_SYMBOLS;
}
static int
@@ -91,12 +97,14 @@ rewrite(struct hex_image *image, unsigned addr, char *data, int len)
static const struct option options[] = {
{ .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
+ { .name = "cal", .has_arg = 1, .val = 'c' },
{ 0, 0, 0, 0},
};
static void usage(char *program)
{
- fprintf(stderr, "usage: %s [--tty <tty-name>] file.ihx serial-number\n", program);
+ fprintf(stderr, "usage: %s [--tty=<tty-name>] [--device=<device-name>] [--cal=<radio-cal>] file.ihx serial-number\n", program);
exit(1);
}
@@ -119,16 +127,27 @@ main (int argc, char **argv)
char serial_int[2];
unsigned int s;
int i;
- unsigned usb_descriptors;
int string_num;
char *tty = NULL;
+ char *device = NULL;
+ uint32_t cal = 0;
+ char cal_int[4];
+ char *cal_end;
int c;
- while ((c = getopt_long(argc, argv, "T:", options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "T:D:c:", options, NULL)) != -1) {
switch (c) {
case 'T':
tty = optarg;
break;
+ case 'D':
+ device = optarg;
+ break;
+ case 'c':
+ cal = strtoul(optarg, &cal_end, 10);
+ if (cal_end == optarg || *cal_end != '\0')
+ usage(argv[0]);
+ break;
default:
usage(argv[0]);
break;
@@ -178,7 +197,7 @@ main (int argc, char **argv)
serial = strtoul(serial_string, NULL, 0);
if (!serial)
-(argv[0]);
+ usage(argv[0]);
serial_int[0] = serial & 0xff;
serial_int[1] = (serial >> 8) & 0xff;
@@ -189,36 +208,55 @@ main (int argc, char **argv)
exit(1);
}
- usb_descriptors = AO_USB_DESCRIPTORS - image->address;
- string_num = 0;
- while (image->data[usb_descriptors] != 0 && usb_descriptors < image->length) {
- if (image->data[usb_descriptors+1] == AO_USB_DESC_STRING) {
- ++string_num;
- if (string_num == 4)
- break;
+ if (AO_USB_DESCRIPTORS) {
+ unsigned usb_descriptors;
+ usb_descriptors = AO_USB_DESCRIPTORS - image->address;
+ string_num = 0;
+ while (image->data[usb_descriptors] != 0 && usb_descriptors < image->length) {
+ if (image->data[usb_descriptors+1] == AO_USB_DESC_STRING) {
+ ++string_num;
+ if (string_num == 4)
+ break;
+ }
+ usb_descriptors += image->data[usb_descriptors];
+ }
+ if (usb_descriptors >= image->length || image->data[usb_descriptors] == 0 ) {
+ fprintf(stderr, "Cannot rewrite serial string at %04x\n", AO_USB_DESCRIPTORS);
+ exit(1);
}
- usb_descriptors += image->data[usb_descriptors];
- }
- if (usb_descriptors >= image->length || image->data[usb_descriptors] == 0 ) {
- fprintf(stderr, "Cannot rewrite serial string at %04x\n", AO_USB_DESCRIPTORS);
- exit(1);
- }
- serial_ucs2_len = image->data[usb_descriptors] - 2;
- serial_ucs2 = malloc(serial_ucs2_len);
- if (!serial_ucs2) {
- fprintf(stderr, "Malloc(%d) failed\n", serial_ucs2_len);
- exit(1);
- }
- s = serial;
- for (i = serial_ucs2_len / 2; i; i--) {
- serial_ucs2[i * 2 - 1] = 0;
- serial_ucs2[i * 2 - 2] = (s % 10) + '0';
- s /= 10;
+ serial_ucs2_len = image->data[usb_descriptors] - 2;
+ serial_ucs2 = malloc(serial_ucs2_len);
+ if (!serial_ucs2) {
+ fprintf(stderr, "Malloc(%d) failed\n", serial_ucs2_len);
+ exit(1);
+ }
+ s = serial;
+ for (i = serial_ucs2_len / 2; i; i--) {
+ serial_ucs2[i * 2 - 1] = 0;
+ serial_ucs2[i * 2 - 2] = (s % 10) + '0';
+ s /= 10;
+ }
+ if (!rewrite(image, usb_descriptors + 2 + image->address, serial_ucs2, serial_ucs2_len))
+ usage(argv[0]);
}
- if (!rewrite(image, usb_descriptors + 2 + image->address, serial_ucs2, serial_ucs2_len))
- usage(argv[0]);
+ if (cal) {
+ cal_int[0] = cal & 0xff;
+ cal_int[1] = (cal >> 8) & 0xff;
+ cal_int[2] = (cal >> 16) & 0xff;
+ cal_int[3] = (cal >> 24) & 0xff;
+ if (!AO_RADIO_CAL) {
+ fprintf(stderr, "Cannot find radio calibration location in image\n");
+ exit(1);
+ }
+ if (!rewrite(image, AO_RADIO_CAL, cal_int, sizeof (cal_int))) {
+ fprintf(stderr, "Cannot rewrite radio calibration at %04x\n", AO_RADIO_CAL);
+ exit(1);
+ }
+ }
+ if (!tty)
+ tty = cc_usbdevs_find_by_arg(device, "TIDongle");
dbg = ccdbg_open(tty);
if (!dbg)
exit (1);
diff --git a/ao-tools/ao-postflight/Makefile.am b/ao-tools/ao-postflight/Makefile.am
new file mode 100644
index 00000000..589d164a
--- /dev/null
+++ b/ao-tools/ao-postflight/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-postflight
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS) $(GNOME_CFLAGS) $(PLPLOT_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) $(PLPLOT_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
index 00000000..7bafb6e1
--- /dev/null
+++ b/ao-tools/ao-postflight/ao-postflight.1
@@ -0,0 +1,61 @@
+.\"
+.\" 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"
+[\-s <summary-file>]
+[\--summary=<summary-file>]
+[\-d <detail-file>]
+[\--detail=<detail-file>]
+[\-r <raw-file>]
+[\--raw=<raw-file>]
+[\-p <plot-file>]
+[\--plot=<plot-file>]
+[\-g <gps-file]
+[\--gps=<gps-file]
+[\-k <kml-file]
+[\--kml=<kml-file]
+{flight.eeprom|flight.telem}
+.SH DESCRIPTION
+.I ao-postflight
+reads the specified flight log and produces several different kinds of
+output.
+.IP Summary
+By default, summary information is shown on stdout. With the --summary
+option, it can be redirected to a file.
+.IP Detail
+When requested with the --detail option, a filtered version of the
+flight position, speed and acceleration are written to the specified
+file.
+.IP Raw
+The --raw option writes the unfiltered, but converted acceleration
+and height data to the specified file.
+.IP Plot
+The --plot option writes plots of height, speed and acceleration to
+the specified file in .svg format
+.IP GPS
+The --gps option writes the recorded GPS data to the specified file in
+three columns.
+.IP KML
+The --kml option writes the recorded GPS data to the specified file in
+Keyhole Markup Language format, which can be displayed in Googleearth.
+.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
index 00000000..e5b55665
--- /dev/null
+++ b/ao-tools/ao-postflight/ao-postflight.c
@@ -0,0 +1,827 @@
+/*
+ * 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"
+#include <plplot/plplot.h>
+
+static const char *state_names[] = {
+ "startup",
+ "idle",
+ "pad",
+ "boost",
+ "fast",
+ "coast",
+ "drogue",
+ "main",
+ "landed",
+ "invalid"
+};
+
+static const char *kml_state_colours[] = {
+ "FF000000",
+ "FF000000",
+ "FF000000",
+ "FF0000FF",
+ "FF4080FF",
+ "FF00FFFF",
+ "FFFF0000",
+ "FF00FF00",
+ "FF000000",
+ "FFFFFFFF"
+};
+
+static int plot_colors[3][3] = {
+ { 0, 0x90, 0 }, /* height */
+ { 0xa0, 0, 0 }, /* speed */
+ { 0, 0, 0xc0 }, /* accel */
+};
+
+#define PLOT_HEIGHT 0
+#define PLOT_SPEED 1
+#define PLOT_ACCEL 2
+
+static void
+plot_perioddata(struct cc_perioddata *d, char *axis_label, char *plot_label,
+ double min_time, double max_time, int plot_type)
+{
+ double *times;
+ double ymin, ymax;
+ int ymin_i, ymax_i;
+ int i;
+ int start, stop;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return;
+
+ times = calloc(stop - start + 1, sizeof (double));
+ for (i = start; i <= stop; i++)
+ times[i-start] = i * d->step / 100.0;
+
+ ymin_i = cc_perioddata_min(d, min_time, max_time);
+ ymax_i = cc_perioddata_max(d, min_time, max_time);
+ ymin = d->data[ymin_i];
+ ymax = d->data[ymax_i];
+ plscol0(1, 0, 0, 0);
+ plscol0(2, plot_colors[plot_type][0], plot_colors[plot_type][1], plot_colors[plot_type][2]);
+ plcol0(1);
+ plenv(times[0], times[stop-start],
+ ymin, ymax, 0, 2);
+ pllab("Time", axis_label, plot_label);
+ plcol0(2);
+ plline(stop - start + 1, times, d->data + start);
+ free(times);
+}
+
+static void
+plot_timedata(struct cc_timedata *d, char *axis_label, char *plot_label,
+ double min_time, double max_time, int plot_type)
+{
+ double *times;
+ double *values;
+ double ymin, ymax;
+ int ymin_i, ymax_i;
+ int i;
+ int start = -1, stop = -1;
+ double start_time = 0, stop_time = 0;
+ int num;
+
+ for (i = 0; i < d->num; i++) {
+ if (start < 0 && d->data[i].time >= min_time) {
+ start_time = d->data[i].time;
+ start = i;
+ }
+ if (d->data[i].time <= max_time) {
+ stop_time = d->data[i].time;
+ stop = i;
+ }
+ }
+
+ times = calloc(stop - start + 1, sizeof (double));
+ values = calloc(stop - start + 1, sizeof (double));
+
+ ymin_i = cc_timedata_min(d, min_time, max_time);
+ ymax_i = cc_timedata_max(d, min_time, max_time);
+ ymin = d->data[ymin_i].value;
+ ymax = d->data[ymax_i].value;
+ for (i = start; i <= stop; i++) {
+ times[i-start] = (d->data[i].time - start_time)/100.0;
+ values[i-start] = d->data[i].value;
+ }
+ plscol0(1, 0, 0, 0);
+ plscol0(2, plot_colors[plot_type][0], plot_colors[plot_type][1], plot_colors[plot_type][2]);
+ plcol0(1);
+ plenv(times[0], times[stop-start], ymin, ymax, 0, 2);
+ pllab("Time", axis_label, plot_label);
+ plcol0(2);
+ plline(stop - start + 1, times, values);
+ free(times);
+ free(values);
+}
+
+static struct cc_perioddata *
+merge_data(struct cc_perioddata *first, struct cc_perioddata *last, double split_time)
+{
+ int i;
+ struct cc_perioddata *pd;
+ int num;
+ double start_time, stop_time;
+ double t;
+
+ pd = calloc(1, sizeof (struct cc_perioddata));
+ start_time = first->start;
+ stop_time = last->start + last->step * last->num;
+ num = (stop_time - start_time) / first->step;
+ pd->num = num;
+ pd->data = calloc(num, sizeof (double));
+ pd->start = first->start;
+ pd->step = first->step;
+ for (i = 0; i < num; i++) {
+ t = pd->start + i * pd->step;
+ if (t <= split_time) {
+ pd->data[i] = first->data[i];
+ } else {
+ int j;
+
+ j = (t - last->start) / last->step;
+ if (j < 0 || j >= last->num)
+ pd->data[i] = 0;
+ else
+ pd->data[i] = last->data[j];
+ }
+ }
+ return pd;
+}
+
+static const char kml_header_start[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
+ "<Document>\n"
+ " <name>%s</name>\n"
+ " <description>\n";
+static const char kml_header_end[] =
+ " </description>\n"
+ " <open>0</open>\n";
+
+static const char kml_style_start[] =
+ " <Style id=\"ao-flightstate-%s\">\n"
+ " <LineStyle><color>%s</color><width>4</width></LineStyle>\n"
+ " <BalloonStyle>\n"
+ " <text>\n";
+static const char kml_style_end[] =
+ " </text>\n"
+ " </BalloonStyle>\n"
+ " </Style>\n";
+
+static const char kml_placemark_start[] =
+ " <Placemark>\n"
+ " <name>%s</name>\n"
+ " <styleUrl>#ao-flightstate-%s</styleUrl>\n"
+ " <LineString>\n"
+ " <tessellate>1</tessellate>\n"
+ " <altitudeMode>absolute</altitudeMode>\n"
+ " <coordinates>\n";
+
+static const char kml_coord_fmt[] =
+ " %12.7f, %12.7f, %12.7f <!-- alt %12.7f time %12.7f sats %d -->\n";
+
+static const char kml_placemark_end[] =
+ " </coordinates>\n"
+ " </LineString>\n"
+ " </Placemark>\n";
+
+static const char kml_footer[] =
+ " </coordinates>\n"
+ " </LineString>\n"
+ " </Placemark>\n"
+ "</Document>\n"
+ "</kml>\n";
+
+static unsigned
+gps_daytime(struct cc_gpselt *gps)
+{
+ return ((gps->hour * 60 +
+ gps->minute) * 60 +
+ gps->second) * 1000;
+}
+
+int
+daytime_hour(unsigned daytime)
+{
+ return daytime / 1000 / 60 / 60;
+}
+
+int
+daytime_minute(unsigned daytime)
+{
+ return (daytime / 1000 / 60) % 60;
+}
+
+int
+daytime_second(unsigned daytime)
+{
+ return (daytime / 1000) % 60;
+}
+
+int
+daytime_millisecond(unsigned daytime)
+{
+ return daytime % 1000;
+}
+
+static unsigned
+compute_daytime_ms(double time, struct cc_gpsdata *gps)
+{
+ int i;
+ unsigned gps_start_daytime, gps_stop_daytime;
+
+ if (time <= gps->data[0].time) {
+ gps_stop_daytime = gps_daytime(&gps->data[0]);
+ return gps_stop_daytime - (gps->data[0].time - time) * 10;
+ }
+ for (i = 0; i < gps->num - 1; i++)
+ if (time > gps->data[i].time)
+ break;
+ gps_start_daytime = gps_daytime(&gps->data[i]);
+ if (i == gps->num - 1) {
+ return gps_start_daytime + (time - gps->data[i].time) * 10;
+ } else {
+ unsigned gps_period_daytime;
+ double gps_period_time;
+ double time_since_start;
+
+ gps_stop_daytime = gps_daytime(&gps->data[i + 1]);
+
+ /* range of gps daytime values */
+ gps_period_daytime = gps_stop_daytime - gps_start_daytime;
+
+ /* range of gps time values */
+ gps_period_time = gps->data[i+1].time - gps->data[i].time;
+
+ /* sample time after first gps time */
+ time_since_start = time - gps->data[i].time;
+
+ return gps_start_daytime +
+ gps_period_daytime * time_since_start / gps_period_time;
+ }
+}
+
+static void
+analyse_flight(struct cc_flightraw *f, FILE *summary_file, FILE *detail_file,
+ FILE *raw_file, char *plot_name, FILE *gps_file, FILE *kml_file)
+{
+ double height;
+ double accel;
+ double speed;
+ double avg_speed;
+ double boost_start, boost_stop;
+ double min_pres;
+ int i;
+ int pres_i, accel_i, speed_i;
+ int boost_start_set = 0;
+ int boost_stop_set = 0;
+ enum ao_flight_state state;
+ double state_start, state_stop;
+ struct cc_flightcooked *cooked;
+ double apogee;
+ char buf[128];
+
+ if (kml_file) {
+ snprintf(buf, sizeof (buf), "AO Flight#%d S/N: %03d", f->flight, f->serial);
+ fprintf(kml_file, kml_header_start, buf);
+ }
+
+ fprintf(summary_file,
+ "Serial: %9d\n"
+ "Flight: %9d\n",
+ f->serial, f->flight);
+
+ if (f->year) {
+ snprintf(buf, sizeof (buf),
+ "Date: %04d-%02d-%02d\n",
+ f->year, f->month, f->day);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+ if (f->gps.num) {
+ snprintf(buf, sizeof (buf),
+ "Time: %2d:%02d:%02d\n",
+ f->gps.data[0].hour,
+ f->gps.data[0].minute,
+ f->gps.data[0].second);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+ 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);
+ if (pres_i >= 0)
+ {
+ min_pres = f->pres.data[pres_i].value;
+ height = cc_barometer_to_altitude(min_pres) -
+ cc_barometer_to_altitude(f->ground_pres);
+ apogee = f->pres.data[pres_i].time;
+ snprintf(buf, sizeof (buf), "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);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+
+ cooked = cc_flight_cook(f);
+ if (cooked) {
+ speed_i = cc_perioddata_max(&cooked->accel_speed, boost_start, boost_stop);
+ if (speed_i >= 0) {
+ speed = cooked->accel_speed.data[speed_i];
+ snprintf(buf, sizeof (buf), "Max speed: %9.2fm/s %9.2fft/s %9.2fs\n",
+ speed, speed * 100 / 2.4 / 12.0,
+ (cooked->accel_speed.start + speed_i * cooked->accel_speed.step - boost_start) / 100.0);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+ }
+ accel_i = cc_timedata_min(&f->accel, boost_start, boost_stop);
+ if (accel_i >= 0)
+ {
+ accel = cc_accelerometer_to_acceleration(f->accel.data[accel_i].value,
+ f->ground_accel);
+ snprintf(buf, sizeof (buf), "Max accel: %9.2fm/s² %9.2fg %9.2fs\n",
+ accel, accel / 9.80665,
+ (f->accel.data[accel_i].time - boost_start) / 100.0);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+
+ if (kml_file)
+ fprintf(kml_file, "%s", kml_header_end);
+
+ for (i = 0; i < f->state.num; i++) {
+ state = f->state.data[i].value;
+ state_start = f->state.data[i].time;
+ while (i < f->state.num - 1 && f->state.data[i+1].value == state)
+ i++;
+ 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;
+ fprintf(summary_file, "State: %s\n", state_names[state]);
+ fprintf(summary_file, "\tStart: %9.2fs\n", (state_start - boost_start) / 100.0);
+ fprintf(summary_file, "\tDuration: %9.2fs\n", (state_stop - state_start) / 100.0);
+ if (kml_file) {
+ fprintf(kml_file, kml_style_start, state_names[state], kml_state_colours[state]);
+ fprintf(kml_file, "\tState: %s\n", state_names[state]);
+ fprintf(kml_file, "\tStart: %9.2fs\n", (state_start - boost_start) / 100.0);
+ fprintf(kml_file, "\tDuration: %9.2fs\n", (state_stop - state_start) / 100.0);
+ }
+
+ accel_i = cc_timedata_min(&f->accel, state_start, state_stop);
+ if (accel_i >= 0)
+ {
+ accel = cc_accelerometer_to_acceleration(f->accel.data[accel_i].value,
+ f->ground_accel);
+ snprintf(buf, sizeof (buf), "\tMax accel: %9.2fm/s² %9.2fg %9.2fs\n",
+ accel, accel / 9.80665,
+ (f->accel.data[accel_i].time - boost_start) / 100.0);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+
+ if (cooked) {
+ if (state < ao_flight_drogue) {
+ speed_i = cc_perioddata_max_mag(&cooked->accel_speed, state_start, state_stop);
+ if (speed_i >= 0)
+ speed = cooked->accel_speed.data[speed_i];
+ avg_speed = cc_perioddata_average(&cooked->accel_speed, state_start, state_stop);
+ } else {
+ speed_i = cc_perioddata_max_mag(&cooked->pres_speed, state_start, state_stop);
+ if (speed_i >= 0)
+ speed = cooked->pres_speed.data[speed_i];
+ avg_speed = cc_perioddata_average(&cooked->pres_speed, state_start, state_stop);
+ }
+ if (speed_i >= 0)
+ {
+ snprintf(buf, sizeof (buf), "\tMax speed: %9.2fm/s %9.2fft/s %9.2fs\n",
+ speed, speed * 100 / 2.4 / 12.0,
+ (cooked->accel_speed.start + speed_i * cooked->accel_speed.step - boost_start) / 100.0);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+
+ snprintf(buf, sizeof (buf), "\tAvg speed: %9.2fm/s %9.2fft/s\n",
+ avg_speed, avg_speed * 100 / 2.4 / 12.0);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+ }
+ pres_i = cc_timedata_min(&f->pres, state_start, state_stop);
+ if (pres_i >= 0)
+ {
+ min_pres = f->pres.data[pres_i].value;
+ height = cc_barometer_to_altitude(min_pres) -
+ cc_barometer_to_altitude(f->ground_pres);
+ snprintf(buf, sizeof (buf), "\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);
+ fprintf(summary_file, "%s", buf);
+ if (kml_file) fprintf(kml_file, "%s", buf);
+ }
+ if (kml_file) fprintf(kml_file, "%s", kml_style_end);
+ }
+ if (cooked && detail_file) {
+ double max_height = 0;
+ int i;
+ double *times;
+
+ fprintf(detail_file, "%9s %9s %9s %9s %9s\n",
+ "time", "height", "speed", "accel", "daytime");
+ for (i = 0; i < cooked->pres_pos.num; i++) {
+ double clock_time = cooked->accel_accel.start + i * cooked->accel_accel.step;
+ double time = (clock_time - boost_start) / 100.0;
+ double accel = cooked->accel_accel.data[i];
+ double pos = cooked->pres_pos.data[i];
+ double speed;
+ unsigned daytime;
+ if (cooked->pres_pos.start + cooked->pres_pos.step * i < apogee)
+ speed = cooked->accel_speed.data[i];
+ else
+ speed = cooked->pres_speed.data[i];
+ if (f->gps.num)
+ daytime = compute_daytime_ms(clock_time, &f->gps);
+ else
+ daytime = 0;
+ fprintf(detail_file, "%9.2f %9.2f %9.2f %9.2f %02d:%02d:%02d.%03d\n",
+ time, pos, speed, accel,
+ daytime_hour(daytime),
+ daytime_minute(daytime),
+ daytime_second(daytime),
+ daytime_millisecond(daytime));
+ }
+ }
+ if (raw_file) {
+ fprintf(raw_file, "%9s %9s %9s %9s\n",
+ "time", "height", "accel", "daytime");
+ for (i = 0; i < cooked->pres.num; i++) {
+ double time = cooked->pres.data[i].time;
+ double pres = cooked->pres.data[i].value;
+ double accel = cooked->accel.data[i].value;
+ unsigned daytime;
+ if (f->gps.num)
+ daytime = compute_daytime_ms(time, &f->gps);
+ else
+ daytime = 0;
+ fprintf(raw_file, "%9.2f %9.2f %9.2f %02d:%02d:%02d.%03d\n",
+ time, pres, accel,
+ daytime_hour(daytime),
+ daytime_minute(daytime),
+ daytime_second(daytime),
+ daytime_millisecond(daytime));
+ }
+ }
+ if (gps_file || kml_file) {
+ int j = 0, baro_pos;
+ double baro_offset;
+ double baro = 0.0;
+ int state_idx = 0;
+
+ if (gps_file)
+ fprintf(gps_file, "%2s %2s %2s %9s %12s %12s %9s %8s %5s\n",
+ "hr", "mn", "sc",
+ "time", "lat", "lon", "alt", "baro", "nsat");
+ if (kml_file)
+ fprintf(kml_file, kml_placemark_start,
+ state_names[(int)f->state.data[state_idx].value],
+ state_names[(int)f->state.data[state_idx].value]);
+
+ if (f->gps.num)
+ baro_offset = f->gps.data[0].alt;
+ else
+ baro_offset = 0;
+ baro_pos = 0;
+ for (i = 0; i < f->gps.num; i++) {
+ int nsat = 0;
+ int k;
+ while (j < f->gps.numsats - 1) {
+ if (f->gps.sats[j].sat[0].time <= f->gps.data[i].time &&
+ f->gps.data[i].time < f->gps.sats[j+1].sat[0].time)
+ break;
+ j++;
+ }
+ if (cooked) {
+ while (baro_pos < cooked->pres_pos.num) {
+ double baro_time = cooked->accel_accel.start + baro_pos * cooked->accel_accel.step;
+ if (baro_time >= f->gps.data[i].time)
+ break;
+ baro_pos++;
+ }
+ if (baro_pos < cooked->pres_pos.num)
+ baro = cooked->pres_pos.data[baro_pos];
+ }
+ if (gps_file)
+ fprintf(gps_file, "%2d %2d %2d %12.7f %12.7f %12.7f %7.1f %7.1f",
+ f->gps.data[i].hour,
+ f->gps.data[i].minute,
+ f->gps.data[i].second,
+ (f->gps.data[i].time - boost_start) / 100.0,
+ f->gps.data[i].lat,
+ f->gps.data[i].lon,
+ f->gps.data[i].alt,
+ baro + baro_offset);
+
+ nsat = 0;
+ if (f->gps.sats) {
+ for (k = 0; k < f->gps.sats[j].nsat; k++) {
+ if (f->gps.sats[j].sat[k].svid != 0)
+ nsat++;
+ }
+ if (gps_file) {
+ fprintf(gps_file, " %4d", nsat);
+ for (k = 0; k < f->gps.sats[j].nsat; k++) {
+ if (f->gps.sats[j].sat[k].svid != 0) {
+ fprintf (gps_file, " %3d(%4.1f)",
+ f->gps.sats[j].sat[k].svid,
+ (double) f->gps.sats[j].sat[k].c_n);
+ }
+ }
+ fprintf(gps_file, "\n");
+ }
+ }
+
+ if (kml_file) {
+ snprintf(buf, sizeof (buf), kml_coord_fmt,
+ f->gps.data[i].lon,
+ f->gps.data[i].lat,
+ baro + baro_offset,
+ f->gps.data[i].alt,
+ (f->gps.data[i].time - boost_start) / 100.0,
+ nsat);
+ fprintf(kml_file, "%s", buf);
+ if (state_idx + 1 < f->state.num && f->state.data[state_idx + 1].time <= f->gps.data[i].time) {
+ state_idx++;
+ if (f->state.data[state_idx - 1].value != f->state.data[state_idx].value) {
+ fprintf(kml_file, "%s", kml_placemark_end);
+ fprintf(kml_file, kml_placemark_start,
+ state_names[(int)f->state.data[state_idx].value],
+ state_names[(int)f->state.data[state_idx].value]);
+ fprintf(kml_file, "%s", buf);
+ }
+ }
+ }
+
+ }
+ if (kml_file)
+ fprintf(kml_file, "%s", kml_footer);
+ }
+ if (cooked && plot_name) {
+ struct cc_perioddata *speed;
+ plsdev("svgcairo");
+ plsfnam(plot_name);
+#define PLOT_DPI 96
+ plspage(PLOT_DPI, PLOT_DPI, 8 * PLOT_DPI, 8 * PLOT_DPI, 0, 0);
+ plscolbg(0xff, 0xff, 0xff);
+ plscol0(1,0,0,0);
+ plstar(2, 3);
+ speed = merge_data(&cooked->accel_speed, &cooked->pres_speed, apogee);
+
+ plot_perioddata(&cooked->pres_pos, "meters", "Height",
+ -1e10, 1e10, PLOT_HEIGHT);
+ plot_perioddata(&cooked->pres_pos, "meters", "Height to Apogee",
+ boost_start, apogee + (apogee - boost_start) / 10.0, PLOT_HEIGHT);
+ plot_perioddata(speed, "meters/second", "Speed",
+ -1e10, 1e10, PLOT_SPEED);
+ plot_perioddata(speed, "meters/second", "Speed to Apogee",
+ boost_start, apogee + (apogee - boost_start) / 10.0, PLOT_SPEED);
+ plot_perioddata(&cooked->accel_accel, "meters/second²", "Acceleration",
+ -1e10, 1e10, PLOT_ACCEL);
+/* plot_perioddata(&cooked->accel_accel, "meters/second²", "Acceleration during Boost",
+ boost_start, boost_stop + (boost_stop - boost_start) / 2.0, PLOT_ACCEL); */
+ plot_timedata(&cooked->accel, "meters/second²", "Acceleration during Boost",
+ boost_start, boost_stop + (boost_stop - boost_start) / 2.0, PLOT_ACCEL);
+ free(speed->data);
+ free(speed);
+ plend();
+ }
+ if (cooked)
+ cc_flightcooked_free(cooked);
+}
+
+static const struct option options[] = {
+ { .name = "summary", .has_arg = 2, .val = 's' },
+ { .name = "detail", .has_arg = 2, .val = 'd' },
+ { .name = "plot", .has_arg = 2, .val = 'p' },
+ { .name = "raw", .has_arg = 2, .val = 'r' },
+ { .name = "gps", .has_arg = 2, .val = 'g' },
+ { .name = "kml", .has_arg = 2, .val = 'k' },
+ { .name = "all", .has_arg = 0, .val = 'a' },
+ { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+ fprintf(stderr, "usage: %s\n"
+ "\t[--all] [-a]\n"
+ "\t[--summary=<summary-file>] [-s <summary-file>]\n"
+ "\t[--detail=<detail-file] [-d <detail-file>]\n"
+ "\t[--raw=<raw-file> -r <raw-file]\n"
+ "\t[--plot=<plot-file> -p <plot-file>]\n"
+ "\t[--gps=<gps-file> -g <gps-file>]\n"
+ "\t[--kml=<kml-file> -k <kml-file>]\n"
+ "\t{flight-log} ...\n", program);
+ exit(1);
+}
+
+char *
+replace_extension(char *file, char *extension)
+{
+ char *slash;
+ char *dot;
+ char *new;
+ int newlen;
+
+ slash = strrchr(file, '/');
+ dot = strrchr(file, '.');
+ if (!dot || (slash && dot < slash))
+ dot = file + strlen(file);
+ newlen = (dot - file) + strlen (extension) + 1;
+ new = malloc (newlen);
+ strncpy (new, file, dot - file);
+ new[dot-file] = '\0';
+ strcat (new, extension);
+ return new;
+}
+
+FILE *
+open_output(char *outname, char *inname, char *extension)
+{
+ char *o;
+ FILE *out;
+
+ if (outname)
+ o = outname;
+ else
+ o = replace_extension(inname, extension);
+ out = fopen(o, "w");
+ if (!out) {
+ perror (o);
+ exit(1);
+ }
+ if (o != outname)
+ free(o);
+ return out;
+}
+
+int
+main (int argc, char **argv)
+{
+ FILE *file;
+ FILE *summary_file = NULL;
+ FILE *detail_file = NULL;
+ FILE *raw_file = NULL;
+ FILE *gps_file = NULL;
+ FILE *kml_file = NULL;
+ int i;
+ int ret = 0;
+ struct cc_flightraw *raw;
+ int c;
+ int serial;
+ char *s;
+ char *summary_name = NULL;
+ char *detail_name = NULL;
+ char *raw_name = NULL;
+ char *plot_name = NULL;
+ char *gps_name = NULL;
+ char *kml_name = NULL;
+ int has_summary = 0;
+ int has_detail = 0;
+ int has_plot = 0;
+ int has_raw = 0;
+ int has_gps = 0;
+ int has_kml = 0;
+ char *this_plot_name = NULL;;
+
+ while ((c = getopt_long(argc, argv, "s:d:p:r:g:k:a", options, NULL)) != -1) {
+ switch (c) {
+ case 's':
+ summary_name = optarg;
+ has_summary = 1;
+ break;
+ case 'd':
+ detail_name = optarg;
+ has_detail = 1;
+ break;
+ case 'p':
+ plot_name = optarg;
+ has_plot = 1;
+ break;
+ case 'r':
+ raw_name = optarg;
+ has_raw = 1;
+ break;
+ case 'g':
+ gps_name = optarg;
+ has_gps = 1;
+ break;
+ case 'k':
+ kml_name = optarg;
+ has_kml = 1;
+ break;
+ case 'a':
+ has_summary = has_detail = has_plot = has_raw = has_gps = has_kml = 1;
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ if (!has_summary)
+ summary_file = stdout;
+ for (i = optind; i < argc; i++) {
+ file = fopen(argv[i], "r");
+ if (!file) {
+ perror(argv[i]);
+ ret++;
+ continue;
+ }
+ if (has_summary && !summary_file)
+ summary_file = open_output(summary_name, argv[i], ".summary");
+ if (has_detail && !detail_file)
+ detail_file = open_output(detail_name, argv[i], ".detail");
+ if (has_plot) {
+ if (plot_name)
+ this_plot_name = plot_name;
+ else
+ this_plot_name = replace_extension(argv[i], ".plot");
+ }
+ if (has_raw && !raw_file)
+ raw_file = open_output(raw_name, argv[i], ".raw");
+ if (has_gps && !gps_file)
+ gps_file = open_output(gps_name, argv[i], ".gps");
+ if (has_kml && !kml_file)
+ kml_file = open_output(kml_name, argv[i], ".kml");
+ 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, summary_file, detail_file, raw_file, this_plot_name, gps_file, kml_file);
+ cc_flightraw_free(raw);
+ if (has_summary && !summary_name) {
+ fclose(summary_file); summary_file = NULL;
+ }
+ if (has_detail && !detail_name) {
+ fclose(detail_file); detail_file = NULL;
+ }
+ if (this_plot_name && this_plot_name != plot_name) {
+ free (this_plot_name); this_plot_name = NULL;
+ }
+ if (has_raw && !raw_name) {
+ fclose(raw_file); raw_file = NULL;
+ }
+ if (has_gps && !gps_name) {
+ fclose(gps_file); gps_file = NULL;
+ }
+ if (has_kml && !kml_name) {
+ fclose(kml_file); kml_file = NULL;
+ }
+ }
+ return ret;
+}
diff --git a/ao-tools/ao-rawload/ao-rawload.1 b/ao-tools/ao-rawload/ao-rawload.1
index e79645f1..30d0467d 100644
--- a/ao-tools/ao-rawload/ao-rawload.1
+++ b/ao-tools/ao-rawload/ao-rawload.1
@@ -21,16 +21,45 @@
ao-rawload \- flash a program to a AltOS device
.SH SYNOPSIS
.B "ao-rawload"
-[\-tty \fItty-device\fP]
+[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
\fIfile.ihx\fP
.SH DESCRIPTION
.I ao-rawload
loads the specified .ihx file, without modification, into the target
-device flash memory.
+device flash or ram (depending on the base address of the .ihx file).
+.SH OPTIONS
+.TP
+\-T tty-device | --tty tty-device
+This selects which tty device the debugger uses to communicate with
+the target device. The special name 'BITBANG' directs ao-dbg to use
+the cp2103 connection, otherwise this should be a usb serial port
+connected to a suitable cc1111 debug node.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleMetrum:2
+.br
+TeleMetrum
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
+.TP
+\-r | --run
+After the file has been loaded, set the PC to the base address of the
+image and resume execution there.
+the .ihx file.
.SH USAGE
.I ao-rawload
reads the specified .ihx file into memory. It then connects to the
specified target device and writes the program to the target device
-flash memory.
+memory and, optionally, starts the program executing.
.SH AUTHOR
Keith Packard
diff --git a/ao-tools/ao-rawload/ao-rawload.c b/ao-tools/ao-rawload/ao-rawload.c
index 1f1537b9..a4746b19 100644
--- a/ao-tools/ao-rawload/ao-rawload.c
+++ b/ao-tools/ao-rawload/ao-rawload.c
@@ -19,15 +19,18 @@
#include <unistd.h>
#include <getopt.h>
#include "ccdbg.h"
+#include "cc.h"
static const struct option options[] = {
{ .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
+ { .name = "run", .has_arg = 0, .val = 'r' },
{ 0, 0, 0, 0},
};
static void usage(char *program)
{
- fprintf(stderr, "usage: %s [--tty <tty-name>] file.ihx\n", program);
+ fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>] [--run] file.ihx\n", program);
exit(1);
}
@@ -42,13 +45,21 @@ main (int argc, char **argv)
char *filename;
FILE *file;
char *tty = NULL;
+ char *device = NULL;
int c;
+ int run = 0;
- while ((c = getopt_long(argc, argv, "T:", options, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "rT:D:", options, NULL)) != -1) {
switch (c) {
case 'T':
tty = optarg;
break;
+ case 'D':
+ device = optarg;
+ break;
+ case 'r':
+ run = 1;
+ break;
default:
usage(argv[0]);
break;
@@ -56,7 +67,7 @@ main (int argc, char **argv)
}
filename = argv[optind];
if (filename == NULL) {
- fprintf(stderr, "usage: %s <filename.ihx>\n", argv[0]);
+ usage(argv[0]);
exit(1);
}
file = fopen(filename, "r");
@@ -75,6 +86,8 @@ main (int argc, char **argv)
}
ccdbg_hex_file_free(hex);
+ if (!tty)
+ tty = cc_usbdevs_find_by_arg(device, "TIDongle");
dbg = ccdbg_open(tty);
if (!dbg)
exit (1);
@@ -98,8 +111,10 @@ main (int argc, char **argv)
ccdbg_close(dbg);
exit(1);
}
- ccdbg_set_pc(dbg, image->address);
- ccdbg_resume(dbg);
+ if (run) {
+ ccdbg_set_pc(dbg, image->address);
+ ccdbg_resume(dbg);
+ }
ccdbg_close(dbg);
exit (0);
}
diff --git a/ao-tools/ao-send-telem/Makefile.am b/ao-tools/ao-send-telem/Makefile.am
new file mode 100644
index 00000000..bfddf131
--- /dev/null
+++ b/ao-tools/ao-send-telem/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-send-telem
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS)
+AO_POSTFLIGHT_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+
+ao_send_telem_DEPENDENCIES = $(AO_POSTFLIGHT_LIBS)
+
+ao_send_telem_LDADD=$(AO_POSTFLIGHT_LIBS) $(LIBUSB_LIBS)
+
+ao_send_telem_SOURCES = ao-send-telem.c
+
+man_MANS = ao-send-telem.1
diff --git a/ao-tools/ao-send-telem/ao-send-telem.1 b/ao-tools/ao-send-telem/ao-send-telem.1
new file mode 100644
index 00000000..fbdb2fe9
--- /dev/null
+++ b/ao-tools/ao-send-telem/ao-send-telem.1
@@ -0,0 +1,64 @@
+.\"
+.\" 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-SEND-TELEM 1 "ao-send-telem" ""
+.SH NAME
+ao-send-telem \- Re-transmit stored telemetry file
+.SH SYNOPSIS
+.B "ao-send-telem"
+[\-T \fItty-device\fP]
+[\--tty \fItty-device\fP]
+[\-D \fIaltos-device\fP]
+[\--device \fIaltos-device\fP]
+[\-F \fIfrequency (kHz)\fP]
+[\--frequency \fIfrequency (kHz)\fP]
+[\-R]
+[\--realtime]
+<flight.telem>
+.SH OPTIONS
+.TP
+\-T tty-device | --tty tty-device
+This selects which tty device ao-dumplog uses to communicate with
+the target device.
+.TP
+\-D AltOS-device | --device AltOS-device
+Search for a connected device. This requires an argument of one of the
+following forms:
+.IP
+TeleDongle:2
+.br
+TeleDongle
+.br
+2
+.IP
+Leaving out the product name will cause the tool to select a suitable
+product, leaving out the serial number will cause the tool to match
+one of the available devices.
+.TP
+\-F kHz | --frequency kHz
+This selects which frequency to send the specified packets on.
+.TP
+\-R | --realtime
+This makes the program delay between packets in pad mode. Normally,
+pad mode packets are sent as quickly as possible.
+.SH DESCRIPTION
+.I ao-send-telem
+reads the specified flight telemetry log and re-transmits it via the
+specified ground station device
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-send-telem/ao-send-telem.c b/ao-tools/ao-send-telem/ao-send-telem.c
new file mode 100644
index 00000000..3db44542
--- /dev/null
+++ b/ao-tools/ao-send-telem/ao-send-telem.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright © 2011 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "cc.h"
+#include "cc-usb.h"
+
+static const struct option options[] = {
+ { .name = "tty", .has_arg = 1, .val = 'T' },
+ { .name = "device", .has_arg = 1, .val = 'D' },
+ { .name = "frequency", .has_arg = 1, .val = 'F' },
+ { .name = "realtime", .has_arg = 0, .val = 'R' },
+ { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+ fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>] [--frequency <kHz>] [--realtime] file.telem ...\n", program);
+ exit(1);
+}
+
+#define bool(b) ((b) ? "true" : "false")
+
+struct ao_telem_list {
+ struct ao_telem_list *next;
+ union ao_telemetry_all telem;
+};
+
+static struct ao_telem_list *telem_list, **telem_last;
+
+static void
+trim_telem(uint16_t time)
+{
+ while (telem_list && (int16_t) (time - telem_list->telem.generic.tick) > 0) {
+ struct ao_telem_list *next = telem_list->next;
+ free(telem_list);
+ telem_list = next;
+ }
+ if (!telem_list)
+ telem_last = &telem_list;
+}
+
+static void
+add_telem(union ao_telemetry_all *telem)
+{
+ struct ao_telem_list *new = malloc (sizeof (struct ao_telem_list));
+ trim_telem((uint16_t) (telem->generic.tick - 20 * 100));
+ new->telem = *telem;
+ new->next = 0;
+ *telem_last = new;
+ telem_last = &new->next;
+}
+
+static enum ao_flight_state cur_state = ao_flight_invalid;
+static enum ao_flight_state last_state = ao_flight_invalid;
+
+static enum ao_flight_state
+packet_state(union ao_telemetry_all *telem)
+{
+ switch (telem->generic.type) {
+ case AO_TELEMETRY_SENSOR_TELEMETRUM:
+ case AO_TELEMETRY_SENSOR_TELEMINI:
+ case AO_TELEMETRY_SENSOR_TELENANO:
+ cur_state = telem->sensor.state;
+ break;
+ case AO_TELEMETRY_MEGA_DATA:
+ cur_state = telem->mega_data.state;
+ break;
+ }
+ return cur_state;
+}
+
+static const char *state_names[] = {
+ "startup",
+ "idle",
+ "pad",
+ "boost",
+ "fast",
+ "coast",
+ "drogue",
+ "main",
+ "landed",
+ "invalid"
+};
+
+static void
+send_telem(struct cc_usb *cc, union ao_telemetry_all *telem)
+{
+ int rssi = (int8_t) telem->generic.rssi / 2 - 74;
+ int i;
+ uint8_t *b;
+
+ packet_state(telem);
+ if (cur_state != last_state) {
+ if (0 <= cur_state && cur_state < sizeof(state_names) / sizeof (state_names[0]))
+ printf ("%s\n", state_names[cur_state]);
+ last_state = cur_state;
+ }
+ cc_usb_printf(cc, "S 20\n");
+ b = (uint8_t *) telem;
+ for (i = 0; i < 0x20; i++)
+ cc_usb_printf(cc, "%02x", b[i]);
+ cc_usb_sync(cc);
+}
+
+static void
+do_delay(uint16_t now, uint16_t then)
+{
+ int16_t delay = (int16_t) (now - then);
+
+ if (delay > 0 && delay < 1000)
+ usleep(delay * 10 * 1000);
+}
+
+static uint16_t
+send_queued(struct cc_usb *cc, int pause)
+{
+ struct ao_telem_list *next;
+ uint16_t tick = 0;
+ int started = 0;
+
+ while (telem_list) {
+ if (started && pause)
+ do_delay(telem_list->telem.generic.tick, tick);
+ tick = telem_list->telem.generic.tick;
+ started = 1;
+ send_telem(cc, &telem_list->telem);
+
+ next = telem_list->next;
+ free(telem_list);
+ telem_list = next;
+ }
+ return tick;
+}
+
+int
+main (int argc, char **argv)
+{
+ struct cc_usb *cc;
+ char *tty = NULL;
+ char *device = NULL;
+ char line[80];
+ int c, i, ret = 0;
+ int freq = 434550;
+ char *s;
+ FILE *file;
+ int serial;
+ uint16_t last_tick;
+ int started;
+ int realtime = 0;
+
+
+ while ((c = getopt_long(argc, argv, "RT:D:F:", options, NULL)) != -1) {
+ switch (c) {
+ case 'T':
+ tty = optarg;
+ break;
+ case 'D':
+ device = optarg;
+ break;
+ case 'F':
+ freq = atoi(optarg);
+ break;
+ case 'R':
+ realtime = 1;
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+ if (!tty)
+ tty = cc_usbdevs_find_by_arg(device, "TeleDongle");
+ if (!tty)
+ tty = getenv("ALTOS_TTY");
+ if (!tty)
+ tty="/dev/ttyACM0";
+ cc = cc_usb_open(tty);
+ if (!cc)
+ exit (1);
+
+ cc_usb_printf(cc, "m 0\n");
+ cc_usb_printf(cc, "c F %d\n", freq);
+ for (i = optind; i < argc; i++) {
+ file = fopen(argv[i], "r");
+ if (!file) {
+ perror(argv[i]);
+ ret++;
+ continue;
+ }
+ started = 0;
+ last_tick = 0;
+ while (fgets(line, sizeof (line), file)) {
+ union ao_telemetry_all telem;
+
+ if (cc_telemetry_parse(line, &telem)) {
+ /*
+ * Skip packets with CRC errors.
+ */
+ if ((telem.generic.status & (1 << 7)) == 0)
+ continue;
+
+ if (started) {
+ do_delay(telem.generic.tick, last_tick);
+ last_tick = telem.generic.tick;
+ send_telem(cc, &telem);
+ } else {
+ enum ao_flight_state state = packet_state(&telem);
+ add_telem(&telem);
+ if (ao_flight_pad < state && state < ao_flight_landed) {
+ printf ("started\n");
+ started = 1;
+ last_tick = send_queued(cc, realtime);
+ }
+ }
+ }
+ }
+ fclose (file);
+
+ }
+ return ret;
+}
diff --git a/ao-tools/ao-stmload/.gitignore b/ao-tools/ao-stmload/.gitignore
new file mode 100644
index 00000000..dedb0094
--- /dev/null
+++ b/ao-tools/ao-stmload/.gitignore
@@ -0,0 +1 @@
+ao-stmload
diff --git a/ao-tools/ao-stmload/Makefile.am b/ao-tools/ao-stmload/Makefile.am
new file mode 100644
index 00000000..5aea7db4
--- /dev/null
+++ b/ao-tools/ao-stmload/Makefile.am
@@ -0,0 +1,15 @@
+if LIBSTLINK
+
+bin_PROGRAMS=ao-stmload
+
+LIBSTLINKDIR=/local/src/stlink
+
+AM_CFLAGS=$(LIBSTLINK_CFLAGS) $(LIBUSB_CFLAGS) -I../lib
+
+ao_stmload_LDADD=$(LIBSTLINK_LIBS) $(LIBUSB_LIBS) -lelf
+
+ao_stmload_SOURCES=ao-stmload.c
+
+man_MANS = ao-stmload.1
+
+endif
diff --git a/ao-tools/ao-stmload/ao-stmload.1 b/ao-tools/ao-stmload/ao-stmload.1
new file mode 100644
index 00000000..38e9c177
--- /dev/null
+++ b/ao-tools/ao-stmload/ao-stmload.1
@@ -0,0 +1,61 @@
+.\"
+.\" 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-LOAD 1 "ao-stmload" ""
+.SH NAME
+ao-stmload \- flash a program to an STM32-based AltOS device
+.SH SYNOPSIS
+.B "ao-stmload"
+[\-D \fI/dev/sgX\fP]
+[\--device \fI/dev/sgX\fP]
+[\--cal \fIradio-calibration\fP]
+[\--serial \fserial-number\fP]
+\fIfile.elf\fP
+.SH DESCRIPTION
+.I ao-stmload
+loads the specified .elf file into the target device flash memory,
+using either existing serial number and radio calibration values or
+taking either of those from the command line.
+.SH OPTIONS
+.TP
+\-D /dev/sgX | --device /dev/sgX
+This targets an STlinkV1 connection rather than STlinkV2
+.TP
+\-s serial-number | --serial serial-number
+This programs the device serial number into the image. If no serial
+number is specified, then the existing serial number, if any, will be
+read from the device.
+.TP
+\-c radio-calibration | --cal radio-calibration This programs the
+radio calibration value into the image for hardware which doesn't have
+any eeprom storage for this value. If no calibration value is
+specified, an existing calibration value will be used. The value here
+can be computed given the current radio calibration value, the
+measured frequency and the desired frequency:
+.IP
+ cal' = cal * (desired/measured)
+.IP
+The default calibration value is 7119667.
+.SH USAGE
+.I ao-stmload
+reads the specified .elf file into memory, edits the image to
+customize it using the specified serial number and radio calibration
+values. It then connects to the debug dongle and writes the program to
+the target device flash memory.
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-stmload/ao-stmload.c b/ao-tools/ao-stmload/ao-stmload.c
new file mode 100644
index 00000000..a471dcc4
--- /dev/null
+++ b/ao-tools/ao-stmload/ao-stmload.c
@@ -0,0 +1,598 @@
+/*
+ * Copyright © 2012 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 <err.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include "stlink-common.h"
+
+#define AO_USB_DESC_STRING 3
+
+struct sym {
+ unsigned addr;
+ char *name;
+ int required;
+} ao_symbols[] = {
+
+ { 0, "ao_romconfig_version", 1 },
+#define AO_ROMCONFIG_VERSION (ao_symbols[0].addr)
+
+ { 0, "ao_romconfig_check", 1 },
+#define AO_ROMCONFIG_CHECK (ao_symbols[1].addr)
+
+ { 0, "ao_serial_number", 1 },
+#define AO_SERIAL_NUMBER (ao_symbols[2].addr)
+
+ { 0, "ao_usb_descriptors", 0 },
+#define AO_USB_DESCRIPTORS (ao_symbols[3].addr)
+
+ { 0, "ao_radio_cal", 0 },
+#define AO_RADIO_CAL (ao_symbols[4].addr)
+};
+
+#define NUM_SYMBOLS 5
+#define NUM_REQUIRED_SYMBOLS 3
+
+/*
+ * Look through the Elf file for the AltOS symbols
+ * that can be adjusted before the image is written
+ * to the device
+ */
+static int
+find_symbols (Elf *e)
+{
+ Elf_Scn *scn;
+ Elf_Data *symbol_data = NULL;
+ GElf_Shdr shdr;
+ GElf_Sym sym;
+ int i, symbol_count, s;
+ int required = 0;
+ char *symbol_name;
+
+ /*
+ * Find the symbols
+ */
+
+ scn = NULL;
+ while ((scn = elf_nextscn(e, scn)) != NULL) {
+ if (gelf_getshdr(scn, &shdr) != &shdr)
+ return 0;
+
+ if (shdr.sh_type == SHT_SYMTAB) {
+ symbol_data = elf_getdata(scn, NULL);
+ symbol_count = shdr.sh_size / shdr.sh_entsize;
+ break;
+ }
+ }
+
+ if (!symbol_data)
+ return 0;
+
+ for (i = 0; i < symbol_count; i++) {
+ gelf_getsym(symbol_data, i, &sym);
+
+ symbol_name = elf_strptr(e, shdr.sh_link, sym.st_name);
+
+ for (s = 0; s < NUM_SYMBOLS; s++)
+ if (!strcmp (ao_symbols[s].name, symbol_name)) {
+ int t;
+ ao_symbols[s].addr = sym.st_value;
+ if (ao_symbols[s].required)
+ ++required;
+ }
+ }
+
+ return required >= NUM_REQUIRED_SYMBOLS;
+}
+
+struct load {
+ uint32_t addr;
+ uint32_t len;
+ uint8_t buf[0];
+};
+
+uint32_t round4(uint32_t a) {
+ return (a + 3) & ~3;
+}
+
+struct load *
+new_load (uint32_t addr, uint32_t len)
+{
+ struct load *new;
+
+ len = round4(len);
+ new = calloc (1, sizeof (struct load) + len);
+ if (!new)
+ abort();
+
+ new->addr = addr;
+ new->len = len;
+ return new;
+}
+
+void
+load_paste(struct load *into, struct load *from)
+{
+ if (from->addr < into->addr || into->addr + into->len < from->addr + from->len)
+ abort();
+
+ memcpy(into->buf + from->addr - into->addr, from->buf, from->len);
+}
+
+/*
+ * Make a new load structure large enough to hold the old one and
+ * the new data
+ */
+struct load *
+expand_load(struct load *from, uint32_t addr, uint32_t len)
+{
+ struct load *new;
+
+ if (from) {
+ uint32_t from_last = from->addr + from->len;
+ uint32_t last = addr + len;
+
+ if (addr > from->addr)
+ addr = from->addr;
+ if (last < from_last)
+ last = from_last;
+
+ len = last - addr;
+
+ if (addr == from->addr && len == from->len)
+ return from;
+ }
+ new = new_load(addr, len);
+ if (from) {
+ load_paste(new, from);
+ free (from);
+ }
+ return new;
+}
+
+/*
+ * Create a new load structure with data from the existing one
+ * and the new data
+ */
+struct load *
+load_write(struct load *from, uint32_t addr, uint32_t len, void *data)
+{
+ struct load *new;
+
+ new = expand_load(from, addr, len);
+ memcpy(new->buf + addr - new->addr, data, len);
+ return new;
+}
+
+/*
+ * Construct a large in-memory block for all
+ * of the loaded sections of the program
+ */
+static struct load *
+get_load(Elf *e)
+{
+ Elf_Scn *scn;
+ size_t shstrndx;
+ GElf_Shdr shdr;
+ Elf_Data *data;
+ uint8_t *buf;
+ char *got_name;
+ size_t nphdr;
+ int p;
+ GElf_Phdr phdr;
+ struct load *load = NULL;
+
+ if (elf_getshdrstrndx(e, &shstrndx) < 0)
+ return 0;
+
+ if (elf_getphdrnum(e, &nphdr) < 0)
+ return 0;
+
+ /*
+ * As far as I can tell, all of the phdr sections should
+ * be flashed to memory
+ */
+ for (p = 0; p < nphdr; p++) {
+
+ /* Find this phdr */
+ gelf_getphdr(e, p, &phdr);
+
+ /* Get the associated file section */
+ scn = gelf_offscn(e, phdr.p_offset);
+
+ if (gelf_getshdr(scn, &shdr) != &shdr)
+ abort();
+
+ data = elf_getdata(scn, NULL);
+
+ /* Write the section data into the memory block */
+ load = load_write(load, phdr.p_paddr, phdr.p_filesz, data->d_buf);
+ }
+ return load;
+}
+
+/*
+ * Edit the to-be-written memory block
+ */
+static int
+rewrite(struct load *load, unsigned addr, uint8_t *data, int len)
+{
+ int i;
+
+ if (addr < load->addr || load->addr + load->len < addr + len)
+ return 0;
+
+ printf("rewrite %04x:", addr);
+ for (i = 0; i < len; i++)
+ printf (" %02x", load->buf[addr - load->addr + i]);
+ printf(" ->");
+ for (i = 0; i < len; i++)
+ printf (" %02x", data[i]);
+ printf("\n");
+ memcpy(load->buf + addr - load->addr, data, len);
+}
+
+/*
+ * Open the specified ELF file and
+ * check for the symbols we need
+ */
+
+Elf *
+ao_open_elf(char *name)
+{
+ int fd;
+ Elf *e;
+ Elf_Scn *scn;
+ Elf_Data *symbol_data = NULL;
+ GElf_Shdr shdr;
+ GElf_Sym sym;
+ size_t n, shstrndx, sz;
+ int i, symbol_count, s;
+ int required = 0;
+
+ if (elf_version(EV_CURRENT) == EV_NONE)
+ return NULL;
+
+ fd = open(name, O_RDONLY, 0);
+
+ if (fd < 0)
+ return NULL;
+
+ e = elf_begin(fd, ELF_C_READ, NULL);
+
+ if (!e)
+ return NULL;
+
+ if (elf_kind(e) != ELF_K_ELF)
+ return NULL;
+
+ if (elf_getshdrstrndx(e, &shstrndx) != 0)
+ return NULL;
+
+ if (!find_symbols(e)) {
+ fprintf (stderr, "Cannot find required symbols\n");
+ return NULL;
+ }
+
+ return e;
+}
+
+/*
+ * Read a 32-bit value from the target device with arbitrary
+ * alignment
+ */
+static uint32_t
+get_uint32(stlink_t *sl, uint32_t addr)
+{
+ const uint8_t *data = sl->q_buf;
+ uint32_t actual_addr;
+ int off;
+ uint32_t result;
+
+ sl->q_len = 0;
+
+ printf ("read 0x%x\n", addr);
+
+ actual_addr = addr & ~3;
+
+ stlink_read_mem32(sl, actual_addr, 8);
+
+ if (sl->q_len != 8)
+ abort();
+
+ off = addr & 3;
+ result = data[off] | (data[off + 1] << 8) | (data[off+2] << 16) | (data[off+3] << 24);
+ printf ("read 0x%08x = 0x%08x\n", addr, result);
+ return result;
+}
+
+/*
+ * Read a 16-bit value from the target device with arbitrary
+ * alignment
+ */
+static uint16_t
+get_uint16(stlink_t *sl, uint32_t addr)
+{
+ const uint8_t *data = sl->q_buf;
+ uint32_t actual_addr;
+ int off;
+ uint16_t result;
+
+ sl->q_len = 0;
+
+
+ actual_addr = addr & ~3;
+
+ stlink_read_mem32(sl, actual_addr, 8);
+
+ if (sl->q_len != 8)
+ abort();
+
+ off = addr & 3;
+ result = data[off] | (data[off + 1] << 8);
+ printf ("read 0x%08x = 0x%04x\n", addr, result);
+ return result;
+}
+
+/*
+ * Check to see if the target device has been
+ * flashed with a similar firmware image before
+ *
+ * This is done by looking for the same romconfig version,
+ * which should be at the same location as the linker script
+ * places this at 0x100 from the start of the rom section
+ */
+static int
+check_flashed(stlink_t *sl)
+{
+ uint16_t romconfig_version = get_uint16(sl, AO_ROMCONFIG_VERSION);
+ uint16_t romconfig_check = get_uint16(sl, AO_ROMCONFIG_CHECK);
+
+ if (romconfig_version != (uint16_t) ~romconfig_check) {
+ fprintf (stderr, "Device has not been flashed before\n");
+ return 0;
+ }
+ return 1;
+}
+
+static const struct option options[] = {
+ { .name = "device", .has_arg = 1, .val = 'D' },
+ { .name = "cal", .has_arg = 1, .val = 'c' },
+ { .name = "serial", .has_arg = 1, .val = 's' },
+ { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+ fprintf(stderr, "usage: %s [--cal=<radio-cal>] [--serial=<serial>] file.elf\n", program);
+ exit(1);
+}
+
+void
+done(stlink_t *sl, int code)
+{
+ if (sl) {
+ stlink_reset(sl);
+ stlink_run(sl);
+ stlink_exit_debug_mode(sl);
+ stlink_close(sl);
+ }
+ exit (code);
+}
+
+int
+main (int argc, char **argv)
+{
+ char *device = NULL;
+ char *filename;
+ Elf *e;
+ char *serial_end;
+ unsigned int serial = 0;
+ char *serial_ucs2;
+ int serial_ucs2_len;
+ char serial_int[2];
+ unsigned int s;
+ int i;
+ int string_num;
+ uint32_t cal = 0;
+ char cal_int[4];
+ char *cal_end;
+ int c;
+ stlink_t *sl;
+ int was_flashed = 0;
+ struct load *load;
+
+ while ((c = getopt_long(argc, argv, "D:c:s:", options, NULL)) != -1) {
+ switch (c) {
+ case 'D':
+ device = optarg;
+ break;
+ case 'c':
+ cal = strtoul(optarg, &cal_end, 10);
+ if (cal_end == optarg || *cal_end != '\0')
+ usage(argv[0]);
+ break;
+ case 's':
+ serial = strtoul(optarg, &serial_end, 10);
+ if (serial_end == optarg || *serial_end != '\0')
+ usage(argv[0]);
+ break;
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ filename = argv[optind];
+ if (filename == NULL)
+ usage(argv[0]);
+
+ /*
+ * Open the source file and load the symbols and
+ * flash data
+ */
+
+ e = ao_open_elf(filename);
+ if (!e) {
+ fprintf(stderr, "Cannot open file \"%s\"\n", filename);
+ exit(1);
+ }
+
+ if (!find_symbols(e)) {
+ fprintf(stderr, "Cannot find symbols in \"%s\"\n", filename);
+ exit(1);
+ }
+
+ if (!(load = get_load(e))) {
+ fprintf(stderr, "Cannot find program data in \"%s\"\n", filename);
+ exit(1);
+ }
+
+ /* Connect to the programming dongle
+ */
+
+ if (device) {
+ sl = stlink_v1_open(50);
+ } else {
+ sl = stlink_open_usb(50);
+
+ }
+ if (!sl) {
+ fprintf (stderr, "No STLink devices present\n");
+ done (sl, 1);
+ }
+
+ sl->verbose = 50;
+
+ /* Verify that the loaded image fits entirely within device flash
+ */
+ if (load->addr < sl->flash_base ||
+ sl->flash_base + sl->flash_size < load->addr + load->len) {
+ fprintf (stderr, "\%s\": Invalid memory range 0x%08x - 0x%08x\n", filename,
+ load->addr, load->addr + load->len);
+ done(sl, 1);
+ }
+
+ /* Enter debugging mode
+ */
+ if (stlink_current_mode(sl) == STLINK_DEV_DFU_MODE)
+ stlink_exit_dfu_mode(sl);
+
+ if (stlink_current_mode(sl) != STLINK_DEV_DEBUG_MODE)
+ stlink_enter_swd_mode(sl);
+
+ /* Go fetch existing config values
+ * if available
+ */
+ was_flashed = check_flashed(sl);
+
+ if (!serial) {
+ if (!was_flashed) {
+ fprintf (stderr, "Must provide serial number\n");
+ done(sl, 1);
+ }
+ serial = get_uint16(sl, AO_SERIAL_NUMBER);
+ if (!serial || serial == 0xffff) {
+ fprintf (stderr, "Invalid existing serial %d\n", serial);
+ done(sl, 1);
+ }
+ }
+
+ if (!cal && AO_RADIO_CAL && was_flashed) {
+ cal = get_uint32(sl, AO_RADIO_CAL);
+ if (!cal || cal == 0xffffffff) {
+ fprintf (stderr, "Invalid existing rf cal %d\n", cal);
+ done(sl, 1);
+ }
+ }
+
+ /* Write the config values into the flash image
+ */
+
+ serial_int[0] = serial & 0xff;
+ serial_int[1] = (serial >> 8) & 0xff;
+
+ if (!rewrite(load, AO_SERIAL_NUMBER, serial_int, sizeof (serial_int))) {
+ fprintf(stderr, "Cannot rewrite serial integer at %08x\n",
+ AO_SERIAL_NUMBER);
+ done(sl, 1);
+ }
+
+ if (AO_USB_DESCRIPTORS) {
+ unsigned usb_descriptors;
+ usb_descriptors = AO_USB_DESCRIPTORS - load->addr;
+ string_num = 0;
+
+ while (load->buf[usb_descriptors] != 0 && usb_descriptors < load->len) {
+ if (load->buf[usb_descriptors+1] == AO_USB_DESC_STRING) {
+ ++string_num;
+ if (string_num == 4)
+ break;
+ }
+ usb_descriptors += load->buf[usb_descriptors];
+ }
+ if (usb_descriptors >= load->len || load->buf[usb_descriptors] == 0 ) {
+ fprintf(stderr, "Cannot rewrite serial string at %08x\n", AO_USB_DESCRIPTORS);
+ done(sl, 1);
+ }
+
+ serial_ucs2_len = load->buf[usb_descriptors] - 2;
+ serial_ucs2 = malloc(serial_ucs2_len);
+ if (!serial_ucs2) {
+ fprintf(stderr, "Malloc(%d) failed\n", serial_ucs2_len);
+ done(sl, 1);
+ }
+ s = serial;
+ for (i = serial_ucs2_len / 2; i; i--) {
+ serial_ucs2[i * 2 - 1] = 0;
+ serial_ucs2[i * 2 - 2] = (s % 10) + '0';
+ s /= 10;
+ }
+ if (!rewrite(load, usb_descriptors + 2 + load->addr, serial_ucs2, serial_ucs2_len)) {
+ fprintf (stderr, "Cannot rewrite USB descriptor at %08x\n", AO_USB_DESCRIPTORS);
+ done(sl, 1);
+ }
+ }
+
+ if (cal && AO_RADIO_CAL) {
+ cal_int[0] = cal & 0xff;
+ cal_int[1] = (cal >> 8) & 0xff;
+ cal_int[2] = (cal >> 16) & 0xff;
+ cal_int[3] = (cal >> 24) & 0xff;
+
+ if (!rewrite(load, AO_RADIO_CAL, cal_int, sizeof (cal_int))) {
+ fprintf(stderr, "Cannot rewrite radio calibration at %08x\n", AO_RADIO_CAL);
+ exit(1);
+ }
+ }
+
+ /* And flash the resulting image to the device
+ */
+ if (stlink_write_flash(sl, load->addr, load->buf, load->len) < 0) {
+ fprintf (stderr, "\"%s\": Write failed\n", filename);
+ done(sl, 1);
+ }
+
+ done(sl, 0);
+}
diff --git a/ao-tools/ao-telem/.gitignore b/ao-tools/ao-telem/.gitignore
new file mode 100644
index 00000000..5ab6f649
--- /dev/null
+++ b/ao-tools/ao-telem/.gitignore
@@ -0,0 +1 @@
+ao-telem
diff --git a/ao-tools/ao-telem/Makefile.am b/ao-tools/ao-telem/Makefile.am
new file mode 100644
index 00000000..3436443e
--- /dev/null
+++ b/ao-tools/ao-telem/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS=ao-telem
+
+AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS)
+AO_POSTFLIGHT_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+
+ao_telem_DEPENDENCIES = $(AO_POSTFLIGHT_LIBS)
+
+ao_telem_LDADD=$(AO_POSTFLIGHT_LIBS) $(LIBUSB_LIBS)
+
+ao_telem_SOURCES = ao-telem.c
+
+man_MANS = ao-telem.1
diff --git a/ao-tools/ao-telem/ao-telem.1 b/ao-tools/ao-telem/ao-telem.1
new file mode 100644
index 00000000..8e6699d5
--- /dev/null
+++ b/ao-tools/ao-telem/ao-telem.1
@@ -0,0 +1,61 @@
+.\"
+.\" 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-TELEM 1 "ao-telem" ""
+.SH NAME
+ao-telem \- Analyse a flight log (either telemetry or eeprom)
+.SH SYNOPSIS
+.B "ao-telem"
+[\-s <summary-file>]
+[\--summary=<summary-file>]
+[\-d <detail-file>]
+[\--detail=<detail-file>]
+[\-r <raw-file>]
+[\--raw=<raw-file>]
+[\-p <plot-file>]
+[\--plot=<plot-file>]
+[\-g <gps-file]
+[\--gps=<gps-file]
+[\-k <kml-file]
+[\--kml=<kml-file]
+{flight.eeprom|flight.telem}
+.SH DESCRIPTION
+.I ao-telem
+reads the specified flight log and produces several different kinds of
+output.
+.IP Summary
+By default, summary information is shown on stdout. With the --summary
+option, it can be redirected to a file.
+.IP Detail
+When requested with the --detail option, a filtered version of the
+flight position, speed and acceleration are written to the specified
+file.
+.IP Raw
+The --raw option writes the unfiltered, but converted acceleration
+and height data to the specified file.
+.IP Plot
+The --plot option writes plots of height, speed and acceleration to
+the specified file in .svg format
+.IP GPS
+The --gps option writes the recorded GPS data to the specified file in
+three columns.
+.IP KML
+The --kml option writes the recorded GPS data to the specified file in
+Keyhole Markup Language format, which can be displayed in Googleearth.
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-telem/ao-telem.c b/ao-tools/ao-telem/ao-telem.c
new file mode 100644
index 00000000..e7fc8e26
--- /dev/null
+++ b/ao-tools/ao-telem/ao-telem.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright © 2011 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.
+ */
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include "cc.h"
+
+static const struct option options[] = {
+ { 0, 0, 0, 0},
+};
+
+static void usage(char *program)
+{
+ fprintf(stderr, "usage: %s\n"
+ "\t{flight-log} ...\n", program);
+ exit(1);
+}
+
+#define bool(b) ((b) ? "true" : "false")
+
+int
+main (int argc, char **argv)
+{
+ char line[80];
+ int c, i, ret;
+ char *s;
+ FILE *file;
+ int serial;
+ 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;
+ while (fgets(line, sizeof (line), file)) {
+ union ao_telemetry_all telem;
+ char call[AO_MAX_CALLSIGN+1];
+ char version[AO_MAX_VERSION+1];
+
+ if (cc_telemetry_parse(line, &telem)) {
+ int rssi = (int8_t) telem.generic.rssi / 2 - 74;
+
+ printf ("serial %5d rssi %d status %02x tick %5d type %3d ",
+ telem.generic.serial, rssi, telem.generic.status,
+ telem.generic.tick, telem.generic.type);
+ if ((telem.generic.status & (1 << 7)) == 0) {
+ printf ("CRC error\n");
+ continue;
+ }
+ switch (telem.generic.type) {
+ case AO_TELEMETRY_SENSOR_TELEMETRUM:
+ case AO_TELEMETRY_SENSOR_TELEMINI:
+ case AO_TELEMETRY_SENSOR_TELENANO:
+ printf ("state %1d accel %5d pres %5d ",
+ telem.sensor.state, telem.sensor.accel, telem.sensor.pres);
+ printf ("accel %6.2f speed %6.2f height %5d ",
+ telem.sensor.acceleration / 16.0,
+ telem.sensor.speed / 16.0,
+ telem.sensor.height);
+ printf ("ground_pres %5d ground_accel %5d accel_plus %5d accel_minus %5d\n",
+ telem.sensor.ground_pres,
+ telem.sensor.ground_accel,
+ telem.sensor.accel_plus_g,
+ telem.sensor.accel_minus_g);
+ break;
+ case AO_TELEMETRY_CONFIGURATION:
+ memcpy(call, telem.configuration.callsign, AO_MAX_CALLSIGN);
+ memcpy(version, telem.configuration.version, AO_MAX_VERSION);
+ call[AO_MAX_CALLSIGN] = '\0';
+ version[AO_MAX_CALLSIGN] = '\0';
+ printf ("device %3d flight %5d config %3d.%03d delay %2d main %4d",
+ telem.configuration.device,
+ telem.configuration.flight,
+ telem.configuration.config_major,
+ telem.configuration.config_minor,
+ telem.configuration.apogee_delay,
+ telem.configuration.main_deploy,
+ telem.configuration.flight_log_max);
+ printf (" call %8s version %8s\n", call, version);
+ break;
+ case AO_TELEMETRY_LOCATION:
+ printf ("sats %d flags %s%s%s%s",
+ telem.location.flags & 0xf,
+ (telem.location.flags & (1 << 4)) ? "valid" : "invalid",
+ (telem.location.flags & (1 << 5)) ? ",running" : "",
+ (telem.location.flags & (1 << 6)) ? ",date" : "",
+ (telem.location.flags & (1 << 7)) ? ",course" : "");
+ printf (" alt %5d lat %12.7f lon %12.7f",
+ telem.location.altitude,
+ telem.location.latitude / 1e7,
+ telem.location.longitude / 1e7);
+ if ((telem.location.flags & (1 << 6)) != 0) {
+ printf (" year %2d month %2d day %2d",
+ telem.location.year,
+ telem.location.month,
+ telem.location.day);
+ printf (" hour %2d minute %2d second %2d",
+ telem.location.hour,
+ telem.location.minute,
+ telem.location.second);
+ }
+ printf (" pdop %3.1f hdop %3.1f vdop %3.1f mode %d",
+ telem.location.pdop / 5.0,
+ telem.location.hdop / 5.0,
+ telem.location.vdop / 5.0,
+ telem.location.mode);
+ if ((telem.location.flags & (1 << 7)) != 0)
+ printf (" ground_speed %6.2f climb_rate %6.2f course %d",
+ telem.location.ground_speed / 100.0,
+ telem.location.climb_rate / 100.0,
+ telem.location.course * 2);
+ printf ("\n");
+ break;
+ case AO_TELEMETRY_SATELLITE:
+ printf ("sats %d", telem.satellite.channels);
+ for (c = 0; c < 12 && c < telem.satellite.channels; c++) {
+ printf (" sat %d svid %d c_n_1 %d",
+ c,
+ telem.satellite.sats[c].svid,
+ telem.satellite.sats[c].c_n_1);
+ }
+ printf ("\n");
+ break;
+ case AO_TELEMETRY_MEGA_SENSOR:
+ printf ("accel %5d pres %9d temp %5d accel_x %5d accel_y %5d accel_z %5d gyro_x %5d gyro_y %5d gyro_z %5d mag_x %5d mag_y %5d mag_z %5d\n",
+ telem.mega_sensor.accel,
+ telem.mega_sensor.pres,
+ telem.mega_sensor.temp,
+ telem.mega_sensor.accel_x,
+ telem.mega_sensor.accel_y,
+ telem.mega_sensor.accel_z,
+ telem.mega_sensor.gyro_x,
+ telem.mega_sensor.gyro_y,
+ telem.mega_sensor.gyro_z,
+ telem.mega_sensor.mag_x,
+ telem.mega_sensor.mag_y,
+ telem.mega_sensor.mag_z);
+ break;
+ case AO_TELEMETRY_MEGA_DATA:
+ printf ("state %1d v_batt %5d v_pyro %5d ",
+ telem.mega_data.state,
+ telem.mega_data.v_batt,
+ telem.mega_data.v_pyro);
+ for (c = 0; c < 6; c++)
+ printf ("s%1d %5d ", c,
+ telem.mega_data.sense[c] |
+ (telem.mega_data.sense[c] << 8));
+
+ printf ("ground_pres %5d ground_accel %5d accel_plus %5d accel_minus %5d ",
+ telem.mega_data.ground_pres,
+ telem.mega_data.ground_accel,
+ telem.mega_data.accel_plus_g,
+ telem.mega_data.accel_minus_g);
+
+ printf ("accel %6.2f speed %6.2f height %5d\n",
+ telem.mega_data.acceleration / 16.0,
+ telem.mega_data.speed / 16.0,
+ telem.mega_data.height);
+
+ break;
+ default:
+ printf("\n");
+ }
+ }
+ }
+ fclose (file);
+
+ }
+ return ret;
+}
diff --git a/ao-tools/ao-view/.gitignore b/ao-tools/ao-view/.gitignore
new file mode 100644
index 00000000..24fbc596
--- /dev/null
+++ b/ao-tools/ao-view/.gitignore
@@ -0,0 +1,4 @@
+*.o
+aoview
+aoview_glade.h
+aoview_flite
diff --git a/ao-tools/ao-view/Makefile.am b/ao-tools/ao-view/Makefile.am
new file mode 100644
index 00000000..7a288417
--- /dev/null
+++ b/ao-tools/ao-view/Makefile.am
@@ -0,0 +1,38 @@
+VERSION=$(shell git describe)
+
+AO_VIEW_CFLAGS=-I$(top_srcdir)/ao-tools/lib
+AO_VIEW_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a
+AM_CFLAGS=$(AO_VIEW_CFLAGS) $(GNOME_CFLAGS) $(ALSA_CFLAGS) -I$(top_srcdir)/src -DAOVIEW_VERSION=\"$(VERSION)\" @FLITE_INCS@
+
+bin_PROGRAMS=ao-view
+
+ao_view_DEPENDENCIES=$(AO_VIEW_LIBS)
+ao_view_LDADD=$(GNOME_LIBS) $(FLITE_LIBS) $(ALSA_LIBS) $(AO_VIEW_LIBS) $(LIBUSB_LIBS)
+
+ao_view_SOURCES = \
+ aoview_main.c \
+ aoview_dev_dialog.c \
+ aoview_serial.c \
+ aoview_monitor.c \
+ aoview_state.c \
+ aoview_convert.c \
+ aoview_log.c \
+ aoview_table.c \
+ aoview_util.c \
+ aoview_file.c \
+ aoview_eeprom.c \
+ aoview_voice.c \
+ aoview_replay.c \
+ aoview_label.c \
+ aoview_flite.c \
+ aoview_channel.c \
+ aoview.h
+
+BUILT_SOURCES = aoview_glade.h
+
+CLEANFILES = aoview_glade.h
+
+man_MANS=ao-view.1
+
+aoview_glade.h: aoview.glade
+ sed -e 's/"/\\"/g' -e 's/^/"/' -e 's/$$/"/' $< > $@
diff --git a/ao-tools/ao-view/ao-view.1 b/ao-tools/ao-view/ao-view.1
new file mode 100644
index 00000000..99834c4e
--- /dev/null
+++ b/ao-tools/ao-view/ao-view.1
@@ -0,0 +1,50 @@
+.\"
+.\" 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-VIEW 1 "ao-view" ""
+.SH NAME
+ao-view \- Rocket flight monitor
+.SH SYNOPSIS
+.B "ao-view"
+[\--tty \fItty-device\fP]
+.SH DESCRIPTION
+.I ao-view
+connects to a TeleDongle or TeleMetrum device through a USB serial device.
+It provides a user interface to monitor, record and review rocket flight data.
+.SH OPTIONS
+The usual Gtk+ command line options can be used, along with
+.IP "\--tty"
+This selects a target device to connect at startup time to.
+The target device may also be selected through the user interface.
+.SH USAGE
+When connected to a TeleDongle device, ao-view turns on the radio
+receiver and listens for telemetry packets. It displays the received
+telemetry data, and reports flight status via voice synthesis. All
+received telemetry information is recorded to a file.
+.P
+When connected to a TeleMetrum device, ao-view downloads the eeprom
+data and stores it in a file.
+.SH FILES
+All data log files are recorded into a user-specified directory
+(default ~/AltOS). Files are named using the current date, the serial
+number of the reporting device, the flight number recorded in the data
+and either '.telem' for telemetry data or '.eeprom' for eeprom data.
+.SH "SEE ALSO"
+ao-load(1), ao-eeprom(1)
+.SH AUTHOR
+Keith Packard
diff --git a/ao-tools/ao-view/aoview.glade b/ao-tools/ao-view/aoview.glade
new file mode 100644
index 00000000..c302ad0d
--- /dev/null
+++ b/ao-tools/ao-view/aoview.glade
@@ -0,0 +1,845 @@
+<?xml version="1.0"?>
+<glade-interface>
+ <!-- interface-requires gtk+ 2.16 -->
+ <!-- interface-naming-policy project-wide -->
+ <widget class="GtkWindow" id="aoview">
+ <property name="width_request">900</property>
+ <property name="height_request">700</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">AltOS View</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem1">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem2">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem3">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem4">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem5">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="gtk_main_quit"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem6">
+ <property name="label">gtk-cut</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem7">
+ <property name="label">gtk-copy</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem8">
+ <property name="label">gtk-paste</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem9">
+ <property name="label">gtk-delete</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Device</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu4">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImageMenuItem" id="ao_connect">
+ <property name="label" translatable="yes">_Connect to device</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate_item" handler="gtk_widget_show" object="device_connect_dialog" after="yes"/>
+ <signal name="activate" handler="gtk_widget_show" object="device_connect_dialog" after="yes"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-connect</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="ao_disconnect">
+ <property name="label" translatable="yes">_Disconnect</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="stock">gtk-disconnect</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="seperator">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="ao_savelog">
+ <property name="label" translatable="yes">_Save EEPROM data</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate_item" handler="gtk_widget_show" object="device_connect_dialog" after="yes"/>
+ <signal name="activate" handler="gtk_widget_show" object="device_connect_dialog"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="ao_replay">
+ <property name="label" translatable="yes">_Replay</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate_item" handler="gtk_widget_show" object="ao_replay_dialog" after="yes"/>
+ <signal name="activate" handler="gtk_widget_show" object="ao_replay_dialog"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="stock">gtk-media-play</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Log</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu5">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImageMenuItem" id="log_new">
+ <property name="label" translatable="yes">_New log</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkImageMenuItem" id="file_configure">
+ <property name="label" translatable="yes">_Configure Log</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="gtk_widget_show" object="file_chooser_dialog" after="yes"/>
+ <child internal-child="image">
+ <widget class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-preferences</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Voice</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu6">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkCheckMenuItem" id="voice_enable">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Enable _Voice</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="channel_menu_item">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Channel</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu7">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 0 (434.550MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 1 (434.650MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 2 (434.750MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 3 (434.850MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 4 (434.950MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 5 (435.050MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 6 (435.150MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 7 (435.250MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 8 (435.350MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioMenuItem" id="channel_9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Channel 9 (435.450MHz)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">channel_0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child>
+ <widget class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImageMenuItem" id="imagemenuitem10">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="gtk_widget_show" object="about_dialog" after="yes"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</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 (m)</property>
+ <property name="justify">center</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="state_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">State</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="rssi_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">RSSI (dBm)</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="height_value">
+ <property name="visible">True</property>
+ <property name="ypad">2</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="state_value">
+ <property name="visible">True</property>
+ <property name="ypad">2</property>
+ <property name="label" translatable="yes">pad</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="rssi_value">
+ <property name="visible">True</property>
+ <property name="ypad">2</property>
+ <property name="label" translatable="yes">-50</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <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>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkTreeView" id="dataview_0">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_expanders">False</property>
+ <property name="enable_grid_lines">both</property>
+ </widget>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTreeView" id="dataview_1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_expanders">False</property>
+ <property name="enable_grid_lines">both</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTreeView" id="dataview_2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_expanders">False</property>
+ <property name="enable_grid_lines">both</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="device_connect_dialog">
+ <property name="border_width">5</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTreeView" id="dev_list">
+ <property name="width_request">300</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">False</property>
+ <property name="rules_hint">True</property>
+ <property name="search_column">0</property>
+ <property name="show_expanders">False</property>
+ <property name="level_indentation">1</property>
+ <property name="enable_grid_lines">both</property>
+ <property name="enable_tree_lines">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <widget class="GtkButton" id="cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="response_id">1</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_widget_hide" object="device_connect_dialog" after="yes"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="connect_button">
+ <property name="label">gtk-connect</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkFileChooserDialog" id="file_chooser_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Configure Log Directory</property>
+ <property name="type_hint">dialog</property>
+ <property name="has_separator">False</property>
+ <property name="action">select-folder</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <widget class="GtkButton" id="file_configure_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_widget_hide" object="file_chooser_dialog"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="file_configure_ok">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkMessageDialog" id="file_fail_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Failed to create log</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="transient_for">aoview</property>
+ <property name="message_type">error</property>
+ <property name="buttons">close</property>
+ <property name="text">Cannot create log file</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkMessageDialog" id="dev_open_fail_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Failed to open device</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="transient_for">aoview</property>
+ <property name="message_type">error</property>
+ <property name="buttons">close</property>
+ <property name="text">Cannot open device</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox6">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area6">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkAboutDialog" id="about_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">About AoView</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">normal</property>
+ <property name="transient_for">aoview</property>
+ <property name="has_separator">False</property>
+ <property name="program_name">AoView</property>
+ <property name="copyright" translatable="yes">Copyright &#xA9; 2009 Keith Packard</property>
+ <property name="comments" translatable="yes">AltOS data capture and display.</property>
+ <property name="website">http://altusmetrum.org</property>
+ <property name="license" translatable="yes">AoView 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.
+
+AoView 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 AoView; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</property>
+ <property name="authors">Keith Packard &lt;keithp@keithp.com&gt;</property>
+ <property name="wrap_license">True</property>
+ <signal name="close" handler="gtk_widget_hide" object="about_dialog" after="yes"/>
+ <signal name="response" handler="gtk_widget_hide" object="about_dialog" after="yes"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkMessageDialog" id="ao_save_done">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">EEPROM save complete</property>
+ <property name="type_hint">normal</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="transient_for">aoview</property>
+ <property name="buttons">close</property>
+ <property name="text">Saving EEPROM data as</property>
+ <property name="secondary_text">&lt;filename&gt;</property>
+ <signal name="close" handler="gtk_widget_hide" object="ao_save_done"/>
+ <signal name="response" handler="gtk_widget_hide" object="ao_save_done"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox11">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area11">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkFileChooserDialog" id="ao_replay_dialog">
+ <property name="border_width">5</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="transient_for">aoview</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox10">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area10">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <widget class="GtkButton" id="ao_replay_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_widget_hide" object="ao_replay_dialog"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="ao_replay_ok">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/ao-tools/ao-view/aoview.h b/ao-tools/ao-view/aoview.h
new file mode 100644
index 00000000..b937df7c
--- /dev/null
+++ b/ao-tools/ao-view/aoview.h
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+#ifndef _AOVIEW_H_
+#define _AOVIEW_H_
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <math.h>
+
+#include "cc.h"
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gconf/gconf-client.h>
+
+struct aostate {
+ struct cc_telem data;
+
+ /* derived data */
+
+ struct cc_telem prev_data;
+
+ double report_time;
+
+ gboolean ascent; /* going up? */
+
+ int ground_altitude;
+ int height;
+ double speed;
+ double acceleration;
+ double battery;
+ double temperature;
+ double main_sense;
+ double drogue_sense;
+ double baro_speed;
+
+ int max_height;
+ double max_acceleration;
+ double max_speed;
+
+ struct cc_gps gps;
+ struct cc_gps_tracking gps_tracking;
+
+ int gps_valid;
+ double pad_lat;
+ double pad_lon;
+ double pad_alt;
+ double pad_lat_total;
+ double pad_lon_total;
+ double pad_alt_total;
+ int npad;
+ int prev_npad;
+
+ 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
+
+void
+aoview_monitor_disconnect(void);
+
+gboolean
+aoview_monitor_connect(char *tty);
+
+gboolean
+aoview_monitor_parse(const char *line);
+
+void
+aoview_monitor_set_channel(int channel);
+
+void
+aoview_monitor_reset(void);
+
+struct aoview_serial *
+aoview_serial_open(const char *tty);
+
+void
+aoview_serial_close(struct aoview_serial *serial);
+
+typedef void (*aoview_serial_callback)(gpointer user_data, struct aoview_serial *serial, gint revents);
+
+void
+aoview_serial_set_callback(struct aoview_serial *serial,
+ aoview_serial_callback func);
+
+void
+aoview_serial_printf(struct aoview_serial *serial, char *format, ...);
+
+int
+aoview_serial_read(struct aoview_serial *serial, char *buf, int len);
+
+int
+aoview_serial_getc(struct aoview_serial *serial);
+
+void
+aoview_dev_dialog_init(GladeXML *xml);
+
+void
+aoview_state_notify(struct cc_telem *data);
+
+void
+aoview_state_new(void);
+
+void
+aoview_state_init(GladeXML *xml);
+
+int16_t
+aoview_pres_to_altitude(int16_t pres);
+
+int16_t
+aoview_altitude_to_pres(int16_t alt);
+
+char *
+aoview_fullname (char *dir, char *file);
+
+char *
+aoview_basename(char *file);
+
+GtkTreeViewColumn *
+aoview_add_plain_text_column (GtkTreeView *view, const gchar *title, gint model_column, gint width);
+
+int
+aoview_mkdir(char *dir);
+
+void
+aoview_log_init(GladeXML *xml);
+
+void
+aoview_log_set_serial(int serial);
+
+int
+aoview_log_get_serial(void);
+
+void
+aoview_log_printf(char *format, ...);
+
+void
+aoview_log_new(void);
+
+void
+aoview_table_start(void);
+
+void
+aoview_table_add_row(int column, char *label, char *format, ...);
+
+void
+aoview_table_finish(void);
+
+void
+aoview_table_init(GladeXML *xml);
+
+void
+aoview_table_clear(void);
+
+struct aoview_file;
+
+extern char *aoview_file_dir;
+
+void
+aoview_file_finish(struct aoview_file *file);
+
+gboolean
+aoview_file_start(struct aoview_file *file);
+
+const char *
+aoview_file_name(struct aoview_file *file);
+
+void
+aoview_file_set_serial(struct aoview_file *file, int serial);
+
+int
+aoview_file_get_serial(struct aoview_file *file);
+
+void
+aoview_file_printf(struct aoview_file *file, char *format, ...);
+
+void
+aoview_file_vprintf(struct aoview_file *file, char *format, va_list ap);
+
+struct aoview_file *
+aoview_file_new(char *ext);
+
+void
+aoview_file_destroy(struct aoview_file *file);
+
+void
+aoview_file_init(GladeXML *xml);
+
+/* aoview_eeprom.c */
+
+gboolean
+aoview_eeprom_save(const char *device);
+
+void
+aoview_eeprom_init(GladeXML *xml);
+
+/* aoview_voice.c */
+void aoview_voice_open(void);
+
+void aoview_voice_close(void);
+
+void aoview_voice_speak(char *format, ...);
+
+/* aoview_label.c */
+
+void aoview_label_init(GladeXML *xml);
+
+void
+aoview_label_show(struct aostate *state);
+
+/* aoview_flite.c */
+
+FILE *
+aoview_flite_start(void);
+
+void
+aoview_flite_stop(void);
+
+/* aoview_main.c */
+
+extern char *aoview_tty;
+
+/* aoview_channel.c */
+
+int
+aoview_channel_current(void);
+
+void
+aoview_channel_init(GladeXML *xml);
+
+#endif /* _AOVIEW_H_ */
diff --git a/ao-tools/ao-view/aoview_channel.c b/ao-tools/ao-view/aoview_channel.c
new file mode 100644
index 00000000..959173ca
--- /dev/null
+++ b/ao-tools/ao-view/aoview_channel.c
@@ -0,0 +1,90 @@
+/*
+ * 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 "aoview.h"
+
+
+#define NUM_CHANNEL 10
+
+static GtkRadioMenuItem *channel_item[NUM_CHANNEL];
+
+int
+aoview_channel_current(void)
+{
+ int c;
+
+ for (c = 0; c < NUM_CHANNEL; c++)
+ if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(channel_item[c])))
+ return c;
+ return -1;
+}
+
+static void
+aoview_channel_notify(int channel)
+{
+ if (0 <= channel && channel < NUM_CHANNEL)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(channel_item[channel]), TRUE);
+}
+
+#define ALTOS_CHANNEL_PATH "/apps/aoview/channel"
+
+static void
+aoview_channel_change(GtkWidget *widget, gpointer data)
+{
+ gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
+ int c = (int) data;
+ GConfClient *gconf_client;
+ GError *error;
+
+ if (enabled) {
+ aoview_monitor_set_channel(c);
+ gconf_client = gconf_client_get_default();
+ gconf_client_set_int(gconf_client, ALTOS_CHANNEL_PATH, c, &error);
+ }
+}
+
+void
+aoview_channel_init(GladeXML *xml)
+{
+ int c;
+ GConfClient *gconf_client;
+
+ for (c = 0; c < NUM_CHANNEL; c++) {
+ char name[32];
+
+ sprintf(name, "channel_%d", c);
+ channel_item[c] = GTK_RADIO_MENU_ITEM(glade_xml_get_widget(xml, name));
+ assert(channel_item[c]);
+ g_signal_connect(G_OBJECT(channel_item[c]), "toggled",
+ G_CALLBACK(aoview_channel_change),
+ (gpointer) c);
+ }
+ gconf_client = gconf_client_get_default();
+ c = 0;
+ if (gconf_client)
+ {
+ GError *error;
+
+ error = NULL;
+ c = gconf_client_get_int(gconf_client,
+ ALTOS_CHANNEL_PATH,
+ &error);
+ if (error)
+ c = 0;
+ }
+ aoview_channel_notify(c);
+}
diff --git a/ao-tools/ao-view/aoview_convert.c b/ao-tools/ao-view/aoview_convert.c
new file mode 100644
index 00000000..8ee05e1d
--- /dev/null
+++ b/ao-tools/ao-view/aoview_convert.c
@@ -0,0 +1,66 @@
+/*
+ * 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 "aoview.h"
+
+static int16_t altitude_table[] = {
+#include "altitude.h"
+};
+
+#define ALT_FRAC_SCALE (1 << ALT_FRAC_BITS)
+#define ALT_FRAC_MASK (ALT_FRAC_SCALE - 1)
+
+int16_t
+aoview_pres_to_altitude(int16_t pres)
+{
+ uint8_t o;
+ int16_t part;
+
+ if (pres < 0)
+ pres = 0;
+ o = pres >> ALT_FRAC_BITS;
+ part = pres & ALT_FRAC_MASK;
+
+ return ((int32_t) altitude_table[o] * (ALT_FRAC_SCALE - part) +
+ (int32_t) altitude_table[o+1] * part + (ALT_FRAC_SCALE >> 1)) >> ALT_FRAC_BITS;
+}
+
+int16_t
+aoview_altitude_to_pres(int16_t alt)
+{
+ int16_t span, sub_span;
+ uint8_t l, h, m;
+ int32_t pres;
+
+ l = 0;
+ h = NALT - 1;
+ while ((h - l) != 1) {
+ m = (l + h) >> 1;
+ if (altitude_table[m] < alt)
+ h = m;
+ else
+ l = m;
+ }
+ span = altitude_table[l] - altitude_table[h];
+ sub_span = altitude_table[l] - alt;
+ pres = ((((int32_t) l * (span - sub_span) + (int32_t) h * sub_span) << ALT_FRAC_BITS) + (span >> 1)) / span;
+ if (pres > 32767)
+ pres = 32767;
+ if (pres < 0)
+ pres = 0;
+ return (int16_t) pres;
+}
diff --git a/ao-tools/ao-view/aoview_dev_dialog.c b/ao-tools/ao-view/aoview_dev_dialog.c
new file mode 100644
index 00000000..2ea43203
--- /dev/null
+++ b/ao-tools/ao-view/aoview_dev_dialog.c
@@ -0,0 +1,173 @@
+/*
+ * 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 "aoview.h"
+
+static void
+aoview_dev_dialog_map(GtkWidget *widget, gpointer data)
+{
+ GtkTreeView *dev_list = data;
+ GtkListStore *list_store;
+ GtkTreeIter iter;
+ int ndev, n;
+ struct cc_usbdevs *devs;
+ struct cc_usbdev *dev;
+
+ list_store = gtk_list_store_new(3,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ G_TYPE_STRING);
+
+ devs = cc_usbdevs_scan();
+ if (devs) {
+ for (n = 0; n < devs->ndev; n++) {
+ dev = devs->dev[n];
+ gtk_list_store_append(list_store, &iter);
+ gtk_list_store_set(list_store, &iter,
+ 0, dev->product,
+ 1, dev->serial,
+ 2, dev->tty,
+ -1);
+ }
+ }
+ gtk_tree_view_set_model (dev_list, GTK_TREE_MODEL(list_store));
+ g_object_unref(G_OBJECT(list_store));
+ gtk_tree_view_columns_autosize(dev_list);
+ cc_usbdevs_free(devs);
+}
+
+static GtkMessageDialog *dev_open_fail_dialog;
+
+static void
+aoview_dev_open_failed(char *name)
+{
+ char *utf8_file;
+ utf8_file = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
+ if (!utf8_file)
+ utf8_file = name;
+ gtk_message_dialog_format_secondary_text(dev_open_fail_dialog,
+ "\"%s\"", utf8_file);
+ if (utf8_file != name)
+ g_free(utf8_file);
+ gtk_dialog_run(GTK_DIALOG(dev_open_fail_dialog));
+ gtk_widget_hide(GTK_WIDGET(dev_open_fail_dialog));
+}
+
+gboolean dialog_save_log;
+
+static void
+aoview_dev_selected(GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *string;
+ gtk_tree_model_get(model, iter,
+ 2, &string,
+ -1);
+ if (dialog_save_log) {
+ dialog_save_log = FALSE;
+ if (!aoview_eeprom_save(string))
+ aoview_dev_open_failed(string);
+ } else {
+ if (!aoview_monitor_connect(string))
+ aoview_dev_open_failed(string);
+ }
+}
+
+static GtkWidget *dialog;
+
+static void
+aoview_dev_dialog_connect(GtkWidget *widget, gpointer data)
+{
+ GtkTreeView *dev_list = data;
+ GtkListStore *list_store;
+ GtkTreeSelection *tree_selection;
+
+ list_store = GTK_LIST_STORE(gtk_tree_view_get_model(dev_list));
+ tree_selection = gtk_tree_view_get_selection(dev_list);
+ gtk_tree_selection_selected_foreach(tree_selection,
+ aoview_dev_selected,
+ data);
+ gtk_widget_hide(dialog);
+}
+
+static void
+aoview_dev_disconnect(GtkWidget *widget)
+{
+ aoview_monitor_disconnect();
+}
+
+static void
+aoview_dev_savelog(GtkWidget *widget, gpointer data)
+{
+ dialog_save_log = TRUE;
+ gtk_widget_show(dialog);
+}
+
+#define _(a) a
+
+void
+aoview_dev_dialog_init(GladeXML *xml)
+{
+ GtkTreeView *dev_list;
+ GtkWidget *connect_button;
+ GtkTreeSelection *dev_selection;
+ GtkWidget *ao_disconnect;
+ GtkWidget *ao_savelog;
+
+ dialog = glade_xml_get_widget(xml, "device_connect_dialog");
+ assert(dialog);
+
+ dev_list = GTK_TREE_VIEW(glade_xml_get_widget(xml, "dev_list"));
+ assert(dev_list);
+
+ aoview_add_plain_text_column(dev_list, _("Product"), 0, 16);
+ aoview_add_plain_text_column(dev_list, _("Serial"), 1, 8);
+ aoview_add_plain_text_column(dev_list, _("Device"), 2, 13);
+
+ dev_selection = gtk_tree_view_get_selection(dev_list);
+ gtk_tree_selection_set_mode(dev_selection, GTK_SELECTION_SINGLE);
+
+ g_signal_connect(G_OBJECT(dialog), "map",
+ G_CALLBACK(aoview_dev_dialog_map),
+ dev_list);
+
+ connect_button = glade_xml_get_widget(xml, "connect_button");
+ assert(connect_button);
+
+ g_signal_connect(G_OBJECT(connect_button), "clicked",
+ G_CALLBACK(aoview_dev_dialog_connect),
+ dev_list);
+
+
+ ao_disconnect = glade_xml_get_widget(xml, "ao_disconnect");
+ assert(ao_disconnect);
+
+ g_signal_connect(G_OBJECT(ao_disconnect), "activate",
+ G_CALLBACK(aoview_dev_disconnect),
+ ao_disconnect);
+
+ ao_savelog = glade_xml_get_widget(xml, "ao_savelog");
+ assert(ao_savelog);
+
+ g_signal_connect(G_OBJECT(ao_savelog), "activate",
+ G_CALLBACK(aoview_dev_savelog),
+ dialog);
+ dev_open_fail_dialog = GTK_MESSAGE_DIALOG(glade_xml_get_widget(xml, "dev_open_fail_dialog"));
+ assert(dev_open_fail_dialog);
+}
diff --git a/ao-tools/ao-view/aoview_eeprom.c b/ao-tools/ao-view/aoview_eeprom.c
new file mode 100644
index 00000000..447b83a4
--- /dev/null
+++ b/ao-tools/ao-view/aoview_eeprom.c
@@ -0,0 +1,159 @@
+/*
+ * 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 "aoview.h"
+
+#define EEPROM_LEN 1024
+
+static struct aoview_file *eeprom_file;
+static char eeprom_line[EEPROM_LEN + 1];
+static int eeprom_pos;
+static GtkMessageDialog *eeprom_save_done;
+static GtkWidget *eeprom_save_close;
+static gboolean eeprom_save_shown;
+
+static void
+aoview_eeprom_disconnect(struct aoview_serial *serial)
+{
+ aoview_file_finish(eeprom_file);
+}
+
+static void
+aoview_eeprom_done(struct aoview_serial *serial)
+{
+ gtk_window_set_title(GTK_WINDOW(eeprom_save_done),
+ "EEPROM data saved");
+ gtk_message_dialog_set_markup(eeprom_save_done,
+ "<b>EEPROM data saved as</b>");
+ if (!eeprom_save_shown)
+ gtk_widget_show(GTK_WIDGET(eeprom_save_done));
+ eeprom_save_close = gtk_window_get_default_widget(GTK_WINDOW(eeprom_save_done));
+ if (eeprom_save_close)
+ gtk_widget_set_sensitive(eeprom_save_close, TRUE);
+ aoview_eeprom_disconnect(serial);
+}
+
+static gboolean
+aoview_eeprom_parse(struct aoview_serial *serial,
+ char *line)
+{
+ char cmd;
+ int tick;
+ int a;
+ int b;
+ int serial_number;
+ const char *name;
+ char *utf8_name;
+
+ if (!strcmp(line, "end")) {
+ aoview_eeprom_done(serial);
+ return FALSE;
+ }
+ if (sscanf(line, "serial-number %u", &serial_number) == 1) {
+ aoview_file_set_serial(eeprom_file, serial_number);
+ } else if (sscanf(line, "%c %x %x %x", &cmd, &tick, &a, &b) == 4) {
+ if (cmd == 'F')
+ aoview_file_set_flight(eeprom_file, b);
+ aoview_file_printf(eeprom_file, "%s\n", line);
+ if (cmd == 'S' && a == 8) {
+ aoview_eeprom_done(serial);
+ return FALSE;
+ }
+
+ if (!eeprom_save_shown)
+ {
+ name = aoview_file_name(eeprom_file);
+ if (name) {
+ utf8_name = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
+ if (!utf8_name)
+ utf8_name = (char *) name;
+ gtk_widget_set_sensitive(eeprom_save_close, FALSE);
+ gtk_window_set_title(GTK_WINDOW(eeprom_save_done),
+ "Saving EEPROM data");
+ gtk_message_dialog_set_markup(eeprom_save_done,
+ "<b>Saving EEPROM data as</b>");
+ gtk_message_dialog_format_secondary_text(eeprom_save_done, "%s",
+ utf8_name);
+ if (utf8_name != name)
+ g_free(utf8_name);
+ gtk_container_check_resize(GTK_CONTAINER(eeprom_save_done));
+ gtk_widget_show(GTK_WIDGET(eeprom_save_done));
+ eeprom_save_shown = TRUE;
+ eeprom_save_close = gtk_window_get_default_widget(GTK_WINDOW(eeprom_save_done));
+ if (eeprom_save_close)
+ gtk_widget_set_sensitive(eeprom_save_close, FALSE);
+ }
+ }
+ }
+ return TRUE;
+}
+
+static void
+aoview_eeprom_callback(gpointer user_data,
+ struct aoview_serial *serial,
+ gint revents)
+{
+ int c;
+
+ if (revents & (G_IO_HUP|G_IO_ERR)) {
+ aoview_eeprom_disconnect(serial);
+ return;
+ }
+ if (revents & G_IO_IN) {
+ for (;;) {
+ c = aoview_serial_getc(serial);
+ if (c == -1)
+ break;
+ if (c == '\r')
+ continue;
+ if (c == '\n') {
+ eeprom_line[eeprom_pos] = '\0';
+ if (eeprom_pos)
+ if (!aoview_eeprom_parse(serial, eeprom_line))
+ break;
+ eeprom_pos = 0;
+ } else if (eeprom_pos < EEPROM_LEN)
+ eeprom_line[eeprom_pos++] = c;
+ }
+ }
+}
+
+gboolean
+aoview_eeprom_save(const char *device)
+{
+ struct aoview_serial *serial;
+
+ gtk_widget_hide(GTK_WIDGET(eeprom_save_done));
+ eeprom_save_shown = FALSE;
+ serial = aoview_serial_open(device);
+ if (!serial)
+ return FALSE;
+ aoview_serial_set_callback(serial, aoview_eeprom_callback);
+ aoview_serial_printf(serial, "v\nl\n");
+ return TRUE;
+}
+
+void
+aoview_eeprom_init(GladeXML *xml)
+{
+ eeprom_file = aoview_file_new("eeprom");
+ assert(eeprom_file);
+
+ eeprom_save_done = GTK_MESSAGE_DIALOG(glade_xml_get_widget(xml, "ao_save_done"));
+ assert(eeprom_save_done);
+
+}
diff --git a/ao-tools/ao-view/aoview_file.c b/ao-tools/ao-view/aoview_file.c
new file mode 100644
index 00000000..292887a0
--- /dev/null
+++ b/ao-tools/ao-view/aoview_file.c
@@ -0,0 +1,235 @@
+/*
+ * 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 "aoview.h"
+
+char *aoview_file_dir;
+
+#define ALTOS_DIR_PATH "/apps/aoview/log_dir"
+#define DEFAULT_DIR "AltOS"
+
+struct aoview_file {
+ char *ext;
+ FILE *file;
+ char *name;
+ int failed;
+ int serial;
+ int flight;
+ int sequence;
+};
+
+static void
+aoview_file_save_conf(void)
+{
+ GConfClient *gconf_client;
+
+ gconf_client = gconf_client_get_default();
+ if (gconf_client)
+ {
+ gconf_client_set_string(gconf_client,
+ ALTOS_DIR_PATH,
+ aoview_file_dir,
+ NULL);
+ g_object_unref(G_OBJECT(gconf_client));
+ }
+}
+
+static void
+aoview_file_configure(GtkWidget *widget, gpointer data)
+{
+ GtkFileChooser *chooser = data;
+ aoview_file_dir = gtk_file_chooser_get_filename(chooser);
+ aoview_file_save_conf();
+ gtk_widget_hide(GTK_WIDGET(chooser));
+}
+
+void
+aoview_file_finish(struct aoview_file *file)
+{
+ if (file->file) {
+ fclose(file->file);
+ file->file = NULL;
+ free(file->name);
+ file->name = NULL;
+ }
+ file->failed = 0;
+}
+
+const char *
+aoview_file_name(struct aoview_file *file)
+{
+ return file->name;
+}
+
+static GtkMessageDialog *file_fail_dialog;
+
+static void
+aoview_file_open_failed(char *name)
+{
+ char *utf8_file;
+ utf8_file = g_filename_to_utf8(name, -1, NULL, NULL, NULL);
+ if (!utf8_file)
+ utf8_file = name;
+ gtk_message_dialog_format_secondary_text(file_fail_dialog,
+ "\"%s\"", utf8_file);
+ if (utf8_file != name)
+ g_free(utf8_file);
+ gtk_widget_show(GTK_WIDGET(file_fail_dialog));
+}
+
+gboolean
+aoview_file_start(struct aoview_file *file)
+{
+ char base[50];
+ char seq[20];
+ struct tm tm;
+ time_t now;
+ char *full;
+ int r;
+
+ if (file->file)
+ return TRUE;
+
+ if (file->failed)
+ return FALSE;
+
+ full = cc_make_filename(file->serial, file->flight, file->ext);
+ file->file = fopen(full, "w");
+ if (!file->file) {
+ aoview_file_open_failed(full);
+ free(full);
+ file->failed = 1;
+ return FALSE;
+ } else {
+ setlinebuf(file->file);
+ file->name = full;
+ return TRUE;
+ }
+}
+
+void
+aoview_file_vprintf(struct aoview_file *file, char *format, va_list ap)
+{
+ if (!aoview_file_start(file))
+ return;
+ vfprintf(file->file, format, ap);
+}
+
+void
+aoview_file_printf(struct aoview_file *file, char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ aoview_file_vprintf(file, format, ap);
+ va_end(ap);
+}
+
+struct aoview_file *
+aoview_file_new(char *ext)
+{
+ struct aoview_file *file;
+
+ file = calloc (1, sizeof (struct aoview_file));
+ if (!file)
+ return NULL;
+ file->ext = strdup(ext);
+ if (!file->ext) {
+ free(file);
+ return NULL;
+ }
+ return file;
+}
+
+void
+aoview_file_destroy(struct aoview_file *file)
+{
+ if (file->file)
+ fclose(file->file);
+ if (file->name)
+ free(file->name);
+ free(file->ext);
+ free(file);
+}
+
+void
+aoview_file_set_serial(struct aoview_file *file, int serial)
+{
+ if (serial != file->serial)
+ aoview_file_finish(file);
+ file->serial = serial;
+}
+
+int
+aoview_file_get_serial(struct aoview_file *file)
+{
+ return file->serial;
+}
+
+void
+aoview_file_set_flight(struct aoview_file *file, int flight)
+{
+ if (flight != file->flight)
+ aoview_file_finish(file);
+ file->flight = flight;
+}
+
+int
+aoview_file_get_flight(struct aoview_file *file)
+{
+ return file->flight;
+}
+
+void
+aoview_file_init(GladeXML *xml)
+{
+ GConfClient *gconf_client;
+ char *file_dir = NULL;
+ GtkFileChooser *file_chooser_dialog;
+ GtkWidget *file_configure_ok;
+
+ 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) {
+ aoview_file_dir = aoview_fullname(getenv("HOME"), DEFAULT_DIR);
+ aoview_file_save_conf();
+ } else {
+ aoview_file_dir = strdup(file_dir);
+ }
+
+ file_chooser_dialog = GTK_FILE_CHOOSER(glade_xml_get_widget(xml, "file_chooser_dialog"));
+ assert(file_chooser_dialog);
+ gtk_file_chooser_set_filename(file_chooser_dialog, aoview_file_dir);
+
+ file_configure_ok = glade_xml_get_widget(xml, "file_configure_ok");
+ assert(file_configure_ok);
+
+ g_signal_connect(G_OBJECT(file_configure_ok), "clicked",
+ G_CALLBACK(aoview_file_configure),
+ file_chooser_dialog);
+
+
+ file_fail_dialog = GTK_MESSAGE_DIALOG(glade_xml_get_widget(xml, "file_fail_dialog"));
+ assert(file_fail_dialog);
+}
diff --git a/ao-tools/ao-view/aoview_flite.c b/ao-tools/ao-view/aoview_flite.c
new file mode 100644
index 00000000..abcdc491
--- /dev/null
+++ b/ao-tools/ao-view/aoview_flite.c
@@ -0,0 +1,141 @@
+/*
+ * 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 <stdio.h>
+#include <flite/flite.h>
+#include "aoview.h"
+#include <alsa/asoundlib.h>
+
+cst_voice *register_cmu_us_kal16();
+cst_voice *register_cmu_us_kal();
+
+static cst_voice *voice;
+
+static FILE *pipe_write;
+static GThread *aoview_flite_thread;
+
+static snd_pcm_t *alsa_handle;
+
+gpointer
+aoview_flite_task(gpointer data)
+{
+ FILE *input = data;
+ char line[1024];
+ cst_wave *wave;
+ int rate;
+ int channels;
+ int err;
+ char *samples;
+ int num_samples;
+
+ err = snd_pcm_open(&alsa_handle, "default",
+ SND_PCM_STREAM_PLAYBACK, 0);
+ if (err < 0) {
+ fprintf(stderr, "alsa open failed %s\n",
+ strerror(-err));
+ alsa_handle = NULL;
+ }
+ rate = 0;
+ channels = 0;
+ while (fgets(line, sizeof (line) - 1, input) != NULL) {
+ if (!alsa_handle)
+ continue;
+ wave = flite_text_to_wave(line, voice);
+ if (wave->sample_rate != rate ||
+ wave->num_channels != channels)
+ {
+ rate = wave->sample_rate;
+ channels = wave->num_channels;
+ err = snd_pcm_set_params(alsa_handle,
+ SND_PCM_FORMAT_S16,
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ channels,
+ rate,
+ 1,
+ 100000);
+ if (err < 0)
+ fprintf(stderr, "alsa set_params error %s\n",
+ strerror(-err));
+ }
+ err = snd_pcm_prepare(alsa_handle);
+ if (err < 0)
+ fprintf(stderr, "alsa pcm_prepare error %s\n",
+ strerror(-err));
+ samples = (char *) wave->samples;
+ num_samples = wave->num_samples;
+ while (num_samples > 0) {
+ err = snd_pcm_writei(alsa_handle,
+ samples, num_samples);
+ if (err <= 0) {
+ fprintf(stderr, "alsa write error %s\n",
+ strerror(-err));
+ break;
+ }
+ num_samples -= err;
+ samples += err * 2 * channels;
+ }
+ snd_pcm_drain(alsa_handle);
+ delete_wave(wave);
+ }
+ snd_pcm_close(alsa_handle);
+ alsa_handle = 0;
+ return NULL;
+}
+
+void
+aoview_flite_stop(void)
+{
+ int status;
+ if (pipe_write) {
+ fclose(pipe_write);
+ pipe_write = NULL;
+ }
+ if (aoview_flite_thread) {
+ g_thread_join(aoview_flite_thread);
+ aoview_flite_thread = NULL;
+ }
+}
+
+FILE *
+aoview_flite_start(void)
+{
+ static once;
+ int p[2];
+ GError *error;
+ FILE *pipe_read;
+
+ if (!once) {
+ flite_init();
+#if HAVE_REGISTER_CMU_US_KAL16
+ voice = register_cmu_us_kal16();
+#else
+#if HAVE_REGISTER_CMU_US_KAL
+ voice = register_cmu_us_kal();
+#endif
+#endif
+ if (!voice) {
+ perror("register voice");
+ exit(1);
+ }
+ }
+ aoview_flite_stop();
+ pipe(p);
+ pipe_read = fdopen(p[0], "r");
+ pipe_write = fdopen(p[1], "w");
+ g_thread_create(aoview_flite_task, pipe_read, TRUE, &error);
+ return pipe_write;
+}
diff --git a/ao-tools/ao-view/aoview_label.c b/ao-tools/ao-view/aoview_label.c
new file mode 100644
index 00000000..24313626
--- /dev/null
+++ b/ao-tools/ao-view/aoview_label.c
@@ -0,0 +1,73 @@
+/*
+ * 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 "aoview.h"
+
+static struct {
+ char *name;
+ char *initial_value;
+ GtkLabel *widget;
+} label_widgets[] = {
+ { "height_label", "Height (m)", NULL },
+ { "state_label", "State", NULL },
+ { "rssi_label", "RSSI (dBm)", NULL },
+ { "speed_label", "Speed (m/s)", NULL },
+ { "height_value", "0", NULL },
+ { "state_value", "pad", NULL },
+ { "rssi_value", "-50", NULL },
+ { "speed_value", "0", NULL },
+};
+
+static void
+aoview_label_assign(GtkLabel *widget, char *value)
+{
+ char *markup;
+
+ markup = g_markup_printf_escaped("<span font_weight=\"bold\" size=\"xx-large\">%s</span>", value);
+ gtk_label_set_markup(widget, markup);
+ g_free(markup);
+}
+
+void
+aoview_label_show(struct aostate *state)
+{
+ char line[1024];
+ sprintf(line, "%d", state->height);
+ aoview_label_assign(label_widgets[4].widget, line);
+
+ aoview_label_assign(label_widgets[5].widget, state->data.state);
+
+ 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
+aoview_label_init(GladeXML *xml)
+{
+ int i;
+ for (i = 0; i < sizeof(label_widgets)/sizeof(label_widgets[0]); i++) {
+ label_widgets[i].widget = GTK_LABEL(glade_xml_get_widget(xml, label_widgets[i].name));
+ aoview_label_assign(label_widgets[i].widget, label_widgets[i].initial_value);
+ assert(label_widgets[i].widget);
+ }
+}
diff --git a/ao-tools/ao-view/aoview_log.c b/ao-tools/ao-view/aoview_log.c
new file mode 100644
index 00000000..2880ecb1
--- /dev/null
+++ b/ao-tools/ao-view/aoview_log.c
@@ -0,0 +1,82 @@
+/*
+ * 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 "aoview.h"
+
+static struct aoview_file *aoview_log;
+
+void
+aoview_log_new(void)
+{
+ aoview_file_finish(aoview_log);
+ aoview_state_new();
+}
+
+void
+aoview_log_set_serial(int serial)
+{
+ aoview_file_set_serial(aoview_log, serial);
+}
+
+int
+aoview_log_get_serial(void)
+{
+ return aoview_file_get_serial(aoview_log);
+}
+
+void
+aoview_log_set_flight(int flight)
+{
+ aoview_file_set_flight(aoview_log, flight);
+}
+
+int
+aoview_log_get_flight(void)
+{
+ return aoview_file_get_flight(aoview_log);
+}
+
+void
+aoview_log_printf(char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ aoview_file_vprintf(aoview_log, format, ap);
+ va_end(ap);
+}
+
+static void
+aoview_log_new_item(GtkWidget *widget, gpointer data)
+{
+ aoview_file_finish(aoview_log);
+}
+
+void
+aoview_log_init(GladeXML *xml)
+{
+ GtkWidget *log_new;
+
+ aoview_log = aoview_file_new("telem");
+ assert(aoview_log);
+
+ log_new = glade_xml_get_widget(xml, "log_new");
+ assert(log_new);
+ g_signal_connect(G_OBJECT(log_new), "activate",
+ G_CALLBACK(aoview_log_new_item),
+ NULL);
+}
diff --git a/ao-tools/ao-view/aoview_main.c b/ao-tools/ao-view/aoview_main.c
new file mode 100644
index 00000000..f8704b89
--- /dev/null
+++ b/ao-tools/ao-view/aoview_main.c
@@ -0,0 +1,113 @@
+/*
+ * 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 "aoview.h"
+
+static const char aoview_glade[] = {
+#include "aoview_glade.h"
+};
+
+static void usage(void) {
+ printf("aoview [--device|-d device_file]");
+ exit(1);
+}
+
+static void destroy_event(GtkWidget *widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+char *aoview_tty = NULL;
+
+int main(int argc, char **argv)
+{
+ GladeXML *xml = NULL;
+ GtkWidget *mainwindow;
+ GtkAboutDialog *about_dialog;
+
+ static struct option long_options[] = {
+ { "tty", 1, 0, 'T'},
+ { 0, 0, 0, 0 }
+ };
+ for (;;) {
+ int c, temp;
+
+ c = getopt_long_only(argc, argv, "T:", long_options, &temp);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'T':
+ aoview_tty = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ g_thread_init(NULL);
+ gtk_init(&argc, &argv);
+ glade_init();
+
+ xml = glade_xml_new_from_buffer(aoview_glade, sizeof (aoview_glade), NULL, NULL);
+
+ /* connect the signals in the interface */
+ glade_xml_signal_autoconnect(xml);
+
+ /* Hook up the close button. */
+ mainwindow = glade_xml_get_widget(xml, "aoview");
+ assert(mainwindow);
+
+ g_signal_connect (G_OBJECT(mainwindow), "destroy",
+ G_CALLBACK(destroy_event), NULL);
+
+ about_dialog = GTK_ABOUT_DIALOG(glade_xml_get_widget(xml, "about_dialog"));
+ assert(about_dialog);
+ gtk_about_dialog_set_version(about_dialog, AOVIEW_VERSION);
+
+ aoview_voice_init(xml);
+
+ aoview_channel_init(xml);
+
+ aoview_dev_dialog_init(xml);
+
+ aoview_state_init(xml);
+
+ aoview_file_init(xml);
+
+ aoview_log_init(xml);
+
+ aoview_table_init(xml);
+
+ aoview_eeprom_init(xml);
+
+ aoview_replay_init(xml);
+
+ aoview_label_init(xml);
+
+ if (aoview_tty) {
+ if (!aoview_monitor_connect(aoview_tty)) {
+ perror(aoview_tty);
+ exit(1);
+ }
+ }
+ aoview_voice_speak("rocket flight monitor ready\n");
+
+ gtk_main();
+
+ return 0;
+}
diff --git a/ao-tools/ao-view/aoview_monitor.c b/ao-tools/ao-view/aoview_monitor.c
new file mode 100644
index 00000000..1f9937b2
--- /dev/null
+++ b/ao-tools/ao-view/aoview_monitor.c
@@ -0,0 +1,108 @@
+/*
+ * 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 "aoview.h"
+
+static struct aoview_serial *monitor_serial;
+
+#define MONITOR_LEN 1024
+
+static char monitor_line[MONITOR_LEN + 1];
+static int monitor_pos;
+
+void
+aoview_monitor_disconnect(void)
+{
+ if (monitor_serial) {
+ aoview_serial_close(monitor_serial);
+ monitor_serial = NULL;
+ }
+ aoview_log_new();
+}
+
+gboolean
+aoview_monitor_parse(const char *input_line)
+{
+ struct cc_telem telem;
+
+ if (!cc_telem_parse(input_line, &telem))
+ return FALSE;
+ aoview_state_notify(&telem);
+ return TRUE;
+}
+
+static void
+aoview_monitor_callback(gpointer user_data,
+ struct aoview_serial *serial,
+ gint revents)
+{
+ int c;
+
+ if (revents & (G_IO_HUP|G_IO_ERR)) {
+ aoview_monitor_disconnect();
+ return;
+ }
+ if (revents & G_IO_IN) {
+ for (;;) {
+ c = aoview_serial_getc(serial);
+ if (c == -1)
+ break;
+ if (c == '\r')
+ continue;
+ if (c == '\n') {
+ monitor_line[monitor_pos] = '\0';
+ if (monitor_pos) {
+ if (aoview_monitor_parse(monitor_line)) {
+ aoview_log_set_serial(aostate.data.serial);
+ aoview_log_set_flight(aostate.data.flight);
+ if (aoview_log_get_serial())
+ aoview_log_printf ("%s\n", monitor_line);
+ }
+ }
+ monitor_pos = 0;
+ } else if (monitor_pos < MONITOR_LEN)
+ monitor_line[monitor_pos++] = c;
+ }
+ }
+}
+
+void
+aoview_monitor_set_channel(int channel)
+{
+ if (monitor_serial) {
+ aoview_serial_printf(monitor_serial, "m 0\n");
+ aoview_serial_printf(monitor_serial, "c r %d\n", channel);
+ aoview_serial_printf(monitor_serial, "m 1\n");
+ }
+}
+
+gboolean
+aoview_monitor_connect(char *tty)
+{
+ int channel;
+ aoview_monitor_disconnect();
+ monitor_serial = aoview_serial_open(tty);
+ if (!monitor_serial)
+ return FALSE;
+ aoview_table_clear();
+ aoview_state_reset();
+ channel = aoview_channel_current();
+ aoview_monitor_set_channel(channel);
+ aoview_serial_set_callback(monitor_serial,
+ aoview_monitor_callback);
+ return TRUE;
+}
diff --git a/ao-tools/ao-view/aoview_replay.c b/ao-tools/ao-view/aoview_replay.c
new file mode 100644
index 00000000..da7b5d6a
--- /dev/null
+++ b/ao-tools/ao-view/aoview_replay.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "aoview.h"
+
+static GtkFileChooser *replay_dialog;
+static GtkWidget *replay_ok;
+static FILE *replay_file;
+static int replay_tick;
+
+static int
+find_tick(char *line, gboolean *is_pad)
+{
+ char *state = strstr(line, "STATE");
+ if (!state)
+ return -1;
+ state = strchr(state, ' ');
+ if (!state)
+ return -1;
+ while (*state == ' ')
+ state++;
+ *is_pad = strncmp(state, "pad", 3) == 0;
+ while (*state && !isdigit(*state))
+ state++;
+ return atoi(state);
+}
+
+static void
+aoview_replay_close(void)
+{
+ if (replay_file) {
+ fclose(replay_file);
+ replay_file = NULL;
+ }
+}
+
+static char replay_line[1024];
+
+static gboolean
+aoview_replay_read(gpointer data);
+
+static gboolean
+aoview_replay_execute(gpointer data)
+{
+ aoview_monitor_parse(replay_line);
+ g_idle_add(aoview_replay_read, NULL);
+ return FALSE;
+}
+
+static gboolean
+aoview_replay_read(gpointer data)
+{
+ int tick;
+ gboolean is_pad;
+
+ if (!replay_file)
+ return FALSE;
+ if (fgets(replay_line, sizeof (replay_line), replay_file)) {
+ tick = find_tick(replay_line, &is_pad);
+ if (tick >= 0 && replay_tick >= 0 && !is_pad) {
+ while (tick < replay_tick)
+ tick += 65536;
+ g_timeout_add((tick - replay_tick) * 10,
+ aoview_replay_execute,
+ NULL);
+ } else {
+ aoview_replay_execute(NULL);
+ }
+ replay_tick = tick;
+ } else {
+ aoview_replay_close();
+ }
+ return FALSE;
+}
+
+static void
+aoview_replay_open(GtkWidget *widget, gpointer data)
+{
+ char *replay_file_name;
+ GtkWidget *dialog;
+
+ aoview_replay_close();
+ replay_file_name = gtk_file_chooser_get_filename(replay_dialog);
+ replay_file = fopen(replay_file_name, "r");
+ if (!replay_file) {
+ dialog = gtk_message_dialog_new(GTK_WINDOW(replay_dialog),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Error loading file '%s': %s",
+ replay_file_name, g_strerror(errno));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ } else {
+ replay_tick = -1;
+ aoview_state_reset();
+ aoview_replay_read(NULL);
+ }
+ gtk_widget_hide(GTK_WIDGET(replay_dialog));
+}
+
+void
+aoview_replay_init(GladeXML *xml)
+{
+ GtkFileFilter *telem_filter;
+ GtkFileFilter *all_filter;
+ GtkFileFilter *log_filter;
+
+ telem_filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(telem_filter, "*.telem");
+ gtk_file_filter_set_name(telem_filter, "Telemetry Files");
+
+ log_filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(log_filter, "*.log");
+ gtk_file_filter_set_name(log_filter, "Log Files");
+
+ all_filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(all_filter, "*");
+ gtk_file_filter_set_name(all_filter, "All Files");
+
+ replay_dialog = GTK_FILE_CHOOSER(glade_xml_get_widget(xml, "ao_replay_dialog"));
+ assert(replay_dialog);
+ gtk_file_chooser_set_current_folder(replay_dialog, aoview_file_dir);
+ gtk_file_chooser_add_filter(replay_dialog, telem_filter);
+ gtk_file_chooser_add_filter(replay_dialog, log_filter);
+ gtk_file_chooser_add_filter(replay_dialog, all_filter);
+
+ replay_ok = glade_xml_get_widget(xml, "ao_replay_ok");
+ assert(replay_ok);
+ g_signal_connect(G_OBJECT(replay_ok), "clicked",
+ G_CALLBACK(aoview_replay_open),
+ replay_dialog);
+}
diff --git a/ao-tools/ao-view/aoview_serial.c b/ao-tools/ao-view/aoview_serial.c
new file mode 100644
index 00000000..29038b79
--- /dev/null
+++ b/ao-tools/ao-view/aoview_serial.c
@@ -0,0 +1,270 @@
+/*
+ * 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 "aoview.h"
+#include <termios.h>
+
+#define AOVIEW_SERIAL_IN_BUF 64
+#define AOVIEW_SERIAL_OUT_BUF 64
+
+struct aoview_buf {
+ char *buf;
+ int off;
+ int count;
+ int size;
+};
+
+static int
+aoview_buf_write(struct aoview_buf *buf, char *data, int len)
+{
+ if (buf->count + len > buf->size) {
+ int new_size = buf->size * 2;
+ if (new_size == 0)
+ new_size = 1024;
+ if (buf->buf)
+ buf->buf = realloc (buf->buf, new_size);
+ else
+ buf->buf = malloc (new_size);
+ buf->size = new_size;
+ }
+ memcpy(buf->buf + buf->count, data, len);
+ buf->count += len;
+ return len;
+}
+
+static int
+aoview_buf_read(struct aoview_buf *buf, char *data, int len)
+{
+ if (len > buf->count - buf->off)
+ len = buf->count - buf->off;
+ memcpy (data, buf->buf + buf->off, len);
+ buf->off += len;
+ if (buf->off == buf->count)
+ buf->off = buf->count = 0;
+ return len;
+}
+
+static int
+aoview_buf_getc(struct aoview_buf *buf)
+{
+ char b;
+ int r;
+
+ r = aoview_buf_read(buf, &b, 1);
+ if (r == 1)
+ return (int) b;
+ return -1;
+}
+
+static void
+aoview_buf_flush(struct aoview_buf *buf, int fd)
+{
+ int ret;
+
+ if (buf->count > buf->off) {
+ ret = write(fd, buf->buf + buf->off, buf->count - buf->off);
+ if (ret > 0) {
+ buf->off += ret;
+ if (buf->off == buf->count)
+ buf->off = buf->count = 0;
+ }
+ }
+}
+
+static void
+aoview_buf_fill(struct aoview_buf *buf, int fd)
+{
+ int ret;
+
+ while (buf->count >= buf->size) {
+ int new_size = buf->size * 2;
+ buf->buf = realloc (buf->buf, new_size);
+ buf->size = new_size;
+ }
+
+ ret = read(fd, buf->buf + buf->count, buf->size - buf->count);
+ if (ret > 0)
+ buf->count += ret;
+}
+
+static void
+aoview_buf_init(struct aoview_buf *buf)
+{
+ buf->buf = malloc (buf->size = 1024);
+ buf->count = 0;
+}
+
+static void
+aoview_buf_fini(struct aoview_buf *buf)
+{
+ free(buf->buf);
+}
+
+struct aoview_serial {
+ GSource source;
+ int fd;
+ struct termios save_termios;
+ struct aoview_buf in_buf;
+ struct aoview_buf out_buf;
+ GPollFD poll_fd;
+};
+
+
+void
+aoview_serial_printf(struct aoview_serial *serial, char *format, ...)
+{
+ char buf[1024];
+ va_list ap;
+ int ret;
+
+ /* sprintf to a local buffer */
+ va_start(ap, format);
+ ret = vsnprintf(buf, sizeof(buf), format, ap);
+ va_end(ap);
+ if (ret > sizeof(buf)) {
+ fprintf(stderr, "printf overflow for format %s\n",
+ format);
+ }
+
+ /* flush local buffer to the wire */
+ aoview_buf_write(&serial->out_buf, buf, ret);
+ aoview_buf_flush(&serial->out_buf, serial->fd);
+}
+
+int
+aoview_serial_read(struct aoview_serial *serial, char *buf, int len)
+{
+ return aoview_buf_read(&serial->in_buf, buf, len);
+}
+
+int
+aoview_serial_getc(struct aoview_serial *serial)
+{
+ return aoview_buf_getc(&serial->in_buf);
+}
+
+static gboolean
+serial_prepare(GSource *source, gint *timeout)
+{
+ struct aoview_serial *serial = (struct aoview_serial *) source;
+ *timeout = -1;
+
+ if (serial->out_buf.count)
+ serial->poll_fd.events |= G_IO_OUT;
+ else
+ serial->poll_fd.events &= ~G_IO_OUT;
+ return FALSE;
+}
+
+static gboolean
+serial_check(GSource *source)
+{
+ struct aoview_serial *serial = (struct aoview_serial *) source;
+ gint revents = serial->poll_fd.revents;
+
+ if (revents & G_IO_NVAL)
+ return FALSE;
+ if (revents & G_IO_IN)
+ return TRUE;
+ if (revents & G_IO_OUT)
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+serial_dispatch(GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ struct aoview_serial *serial = (struct aoview_serial *) source;
+ aoview_serial_callback func = (aoview_serial_callback) callback;
+ gint revents = serial->poll_fd.revents;
+
+ if (revents & G_IO_IN)
+ aoview_buf_fill(&serial->in_buf, serial->fd);
+
+ if (revents & G_IO_OUT)
+ aoview_buf_flush(&serial->out_buf, serial->fd);
+
+ if (func)
+ (*func)(user_data, serial, revents);
+ return TRUE;
+}
+
+static void
+serial_finalize(GSource *source)
+{
+ struct aoview_serial *serial = (struct aoview_serial *) source;
+
+ aoview_buf_fini(&serial->in_buf);
+ aoview_buf_fini(&serial->out_buf);
+ tcsetattr(serial->fd, TCSAFLUSH, &serial->save_termios);
+ close (serial->fd);
+}
+
+static GSourceFuncs serial_funcs = {
+ serial_prepare,
+ serial_check,
+ serial_dispatch,
+ serial_finalize
+};
+
+struct aoview_serial *
+aoview_serial_open(const char *tty)
+{
+ struct aoview_serial *serial;
+ struct termios termios;
+
+ serial = (struct aoview_serial *) g_source_new(&serial_funcs, sizeof (struct aoview_serial));
+ aoview_buf_init(&serial->in_buf);
+ aoview_buf_init(&serial->out_buf);
+ serial->fd = open (tty, O_RDWR | O_NONBLOCK);
+ if (serial->fd < 0) {
+ g_source_destroy(&serial->source);
+ return NULL;
+ }
+ tcgetattr(serial->fd, &termios);
+ serial->save_termios = termios;
+ cfmakeraw(&termios);
+ tcsetattr(serial->fd, TCSAFLUSH, &termios);
+
+ aoview_serial_printf(serial, "E 0\n");
+ tcdrain(serial->fd);
+ usleep(15*1000);
+ tcflush(serial->fd, TCIFLUSH);
+ serial->poll_fd.fd = serial->fd;
+ serial->poll_fd.events = G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR;
+ g_source_attach(&serial->source, NULL);
+ g_source_add_poll(&serial->source,&serial->poll_fd);
+ aoview_serial_set_callback(serial, NULL);
+ return serial;
+}
+
+void
+aoview_serial_close(struct aoview_serial *serial)
+{
+ g_source_remove_poll(&serial->source, &serial->poll_fd);
+ close(serial->fd);
+ g_source_destroy(&serial->source);
+}
+
+void
+aoview_serial_set_callback(struct aoview_serial *serial,
+ aoview_serial_callback func)
+{
+ g_source_set_callback(&serial->source, (GSourceFunc) func, serial, NULL);
+}
diff --git a/ao-tools/ao-view/aoview_state.c b/ao-tools/ao-view/aoview_state.c
new file mode 100644
index 00000000..505bcddc
--- /dev/null
+++ b/ao-tools/ao-view/aoview_state.c
@@ -0,0 +1,371 @@
+/*
+ * 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 "aoview.h"
+#include <math.h>
+
+static inline double sqr(double a) { return a * a; };
+
+static void
+aoview_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;
+}
+
+static void
+aoview_state_add_deg(int column, char *label, double deg, char pos, char neg)
+{
+ double int_part;
+ double min;
+ char sign = pos;
+
+ if (deg < 0) {
+ deg = -deg;
+ sign = neg;
+ }
+ int_part = floor (deg);
+ min = (deg - int_part) * 60.0;
+ aoview_table_add_row(column, label, "%d°%lf'%c",
+ (int) int_part, min, sign);
+
+}
+
+static char *ascent_states[] = {
+ "boost",
+ "fast",
+ "coast",
+ 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 cc_telem *data, struct aostate *state)
+{
+ int i;
+ double new_height;
+ double height_change;
+ double time_change;
+ double accel_counts_per_mss;
+ int tick_count;
+
+ state->report_time = aoview_time();
+
+ state->prev_data = state->data;
+ state->prev_npad = state->npad;
+ 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;
+ accel_counts_per_mss = ((data->accel_minus_g - data->accel_plus_g) / 2.0) / 9.80665;
+ state->acceleration = (data->ground_accel - data->flight_accel) / accel_counts_per_mss;
+ state->speed = data->flight_vel / (accel_counts_per_mss * 100.0);
+ state->temperature = cc_thermometer_to_temperature(data->temp);
+ state->drogue_sense = cc_ignitor_to_voltage(data->drogue);
+ state->main_sense = cc_ignitor_to_voltage(data->main);
+ state->battery = cc_battery_to_voltage(data->batt);
+ if (!strcmp(data->state, "pad")) {
+ if (data->gps.gps_locked && data->gps.nsat >= 4) {
+ state->npad++;
+ state->pad_lat_total += data->gps.lat;
+ state->pad_lon_total += data->gps.lon;
+ state->pad_alt_total += data->gps.alt;
+ if (state->npad > 1) {
+ state->pad_lat = (state->pad_lat * 31 + data->gps.lat) / 32.0;
+ state->pad_lon = (state->pad_lon * 31 + data->gps.lon) / 32.0;
+ state->pad_alt = (state->pad_alt * 31 + data->gps.alt) / 32.0;
+ } else {
+ state->pad_lat = data->gps.lat;
+ state->pad_lon = data->gps.lon;
+ state->pad_alt = data->gps.alt;
+ }
+ }
+ }
+ state->ascent = FALSE;
+ for (i = 0; ascent_states[i]; i++)
+ if (!strcmp(data->state, ascent_states[i]))
+ state->ascent = TRUE;
+
+ /* Only look at accelerometer data on the way up */
+ if (state->ascent && state->acceleration > state->max_acceleration)
+ state->max_acceleration = state->acceleration;
+ if (state->ascent && state->speed > state->max_speed)
+ state->max_speed = state->speed;
+
+ if (state->height > state->max_height)
+ state->max_height = state->height;
+ state->gps.gps_locked = data->gps.gps_locked;
+ state->gps.gps_connected = data->gps.gps_connected;
+ if (data->gps.gps_locked) {
+ state->gps = data->gps;
+ state->gps_valid = 1;
+ if (state->npad)
+ aoview_great_circle(state->pad_lat, state->pad_lon, state->gps.lat, state->gps.lon,
+ &state->distance, &state->bearing);
+ }
+ if (data->gps_tracking.channels)
+ state->gps_tracking = data->gps_tracking;
+ if (state->npad) {
+ state->gps_height = state->gps.alt - state->pad_alt;
+ } else {
+ state->gps_height = 0;
+ }
+}
+
+void
+aoview_speak_state(struct aostate *state)
+{
+ 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);
+ if (!strcmp(state->prev_data.state, "boost"))
+ aoview_voice_speak("max speed %d meters per second\n",
+ (int) state->max_speed);
+ }
+ if (state->prev_npad < MIN_PAD_SAMPLES && state->npad >= MIN_PAD_SAMPLES)
+ aoview_voice_speak("g p s ready\n");
+}
+
+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 cc_telem *data)
+{
+ struct aostate *state = &aostate;
+ aoview_state_derive(data, state);
+ aoview_table_start();
+
+ if (state->npad >= MIN_PAD_SAMPLES)
+ aoview_table_add_row(0, "Ground state", "ready");
+ else
+ aoview_table_add_row(0, "Ground state", "waiting for gps (%d)",
+ MIN_PAD_SAMPLES - state->npad);
+ aoview_table_add_row(0, "Rocket state", "%s", state->data.state);
+ aoview_table_add_row(0, "Callsign", "%s", state->data.callsign);
+ aoview_table_add_row(0, "Rocket serial", "%d", state->data.serial);
+ aoview_table_add_row(0, "Rocket flight", "%d", state->data.flight);
+
+ aoview_table_add_row(0, "RSSI", "%6ddBm", state->data.rssi);
+ aoview_table_add_row(0, "Height", "%6dm", state->height);
+ aoview_table_add_row(0, "Max height", "%6dm", state->max_height);
+ aoview_table_add_row(0, "Acceleration", "%7.1fm/s²", state->acceleration);
+ aoview_table_add_row(0, "Max acceleration", "%7.1fm/s²", state->max_acceleration);
+ aoview_table_add_row(0, "Speed", "%7.1fm/s", state->ascent ? state->speed : state->baro_speed);
+ aoview_table_add_row(0, "Max Speed", "%7.1fm/s", state->max_speed);
+ aoview_table_add_row(0, "Temperature", "%6.2f°C", state->temperature);
+ aoview_table_add_row(0, "Battery", "%5.2fV", state->battery);
+ aoview_table_add_row(0, "Drogue", "%5.2fV", state->drogue_sense);
+ aoview_table_add_row(0, "Main", "%5.2fV", state->main_sense);
+ aoview_table_add_row(0, "Pad altitude", "%dm", state->ground_altitude);
+ aoview_table_add_row(1, "Satellites", "%d", state->gps.nsat);
+ if (state->gps.gps_locked) {
+ aoview_table_add_row(1, "GPS", "locked");
+ } else if (state->gps.gps_connected) {
+ aoview_table_add_row(1, "GPS", "unlocked");
+ } else {
+ aoview_table_add_row(1, "GPS", "not available");
+ }
+ if (state->gps_valid) {
+ aoview_state_add_deg(1, "Latitude", state->gps.lat, 'N', 'S');
+ aoview_state_add_deg(1, "Longitude", state->gps.lon, 'E', 'W');
+ aoview_table_add_row(1, "GPS altitude", "%d", state->gps.alt);
+ aoview_table_add_row(1, "GPS height", "%d", state->gps_height);
+ aoview_table_add_row(1, "GPS date", "%04d-%02d-%02d",
+ state->gps.gps_time.year,
+ state->gps.gps_time.month,
+ state->gps.gps_time.day);
+ aoview_table_add_row(1, "GPS time", "%02d:%02d:%02d",
+ state->gps.gps_time.hour,
+ state->gps.gps_time.minute,
+ state->gps.gps_time.second);
+ }
+ if (state->gps.gps_extended) {
+ aoview_table_add_row(1, "GPS ground speed", "%7.1fm/s %d°",
+ state->gps.ground_speed,
+ state->gps.course);
+ aoview_table_add_row(1, "GPS climb rate", "%7.1fm/s",
+ state->gps.climb_rate);
+ aoview_table_add_row(1, "GPS precision", "%4.1f(hdop) %3dm(h) %3dm(v)",
+ state->gps.hdop, state->gps.h_error, state->gps.v_error);
+ }
+ if (state->npad) {
+ aoview_table_add_row(1, "Distance from pad", "%5.0fm", state->distance);
+ aoview_table_add_row(1, "Direction from pad", "%4.0f°", state->bearing);
+ aoview_state_add_deg(1, "Pad latitude", state->pad_lat, 'N', 'S');
+ aoview_state_add_deg(1, "Pad longitude", state->pad_lon, 'E', 'W');
+ aoview_table_add_row(1, "Pad GPS alt", "%gm", state->pad_alt);
+ }
+ if (state->gps.gps_connected) {
+ int nsat_vis = 0;
+ int c;
+
+ aoview_table_add_row(2, "Satellites Visible", "%d", state->gps_tracking.channels);
+ for (c = 0; c < state->gps_tracking.channels; c++) {
+ aoview_table_add_row(2, "Satellite id,C/N0",
+ "%3d,%2d",
+ state->gps_tracking.sats[c].svid,
+ state->gps_tracking.sats[c].c_n0);
+ }
+ }
+ aoview_table_finish();
+ aoview_label_show(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
+aoview_state_new(void)
+{
+}
+
+void
+aoview_state_init(GladeXML *xml)
+{
+ aoview_state_new();
+}
diff --git a/ao-tools/ao-view/aoview_table.c b/ao-tools/ao-view/aoview_table.c
new file mode 100644
index 00000000..e75ae670
--- /dev/null
+++ b/ao-tools/ao-view/aoview_table.c
@@ -0,0 +1,83 @@
+/*
+ * 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 "aoview.h"
+
+#define NCOL 3
+
+static GtkTreeView *dataview[NCOL];
+static GtkListStore *datalist[NCOL];
+
+void
+aoview_table_start(void)
+{
+ int col;
+ for (col = 0; col < NCOL; col++)
+ datalist[col] = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+}
+
+void
+aoview_table_add_row(int col, char *label, char *format, ...)
+{
+ char buf[1024];
+ va_list ap;
+ GtkTreeIter iter;
+
+ va_start(ap, format);
+ vsnprintf(buf, sizeof (buf), format, ap);
+ va_end(ap);
+ gtk_list_store_append(datalist[col], &iter);
+ gtk_list_store_set(datalist[col], &iter,
+ 0, label,
+ 1, buf,
+ -1);
+}
+
+void
+aoview_table_finish(void)
+{
+ int col;
+ for (col = 0; col < NCOL; col++) {
+ gtk_tree_view_set_model(dataview[col], GTK_TREE_MODEL(datalist[col]));
+ g_object_unref(G_OBJECT(datalist[col]));
+ gtk_tree_view_columns_autosize(dataview[col]);
+ }
+}
+
+void
+aoview_table_clear(void)
+{
+ int col;
+ for (col = 0; col < NCOL; col++)
+ gtk_tree_view_set_model(dataview[col], NULL);
+}
+
+void
+aoview_table_init(GladeXML *xml)
+{
+ int col;
+
+ for (col = 0; col < NCOL; col++) {
+ char name[32];
+ sprintf(name, "dataview_%d", col);
+ dataview[col] = GTK_TREE_VIEW(glade_xml_get_widget(xml, name));
+ assert(dataview[col]);
+
+ aoview_add_plain_text_column(dataview[col], "Field", 0, 20);
+ aoview_add_plain_text_column(dataview[col], "Value", 1, 32);
+ }
+}
diff --git a/ao-tools/ao-view/aoview_util.c b/ao-tools/ao-view/aoview_util.c
new file mode 100644
index 00000000..6ea62ac9
--- /dev/null
+++ b/ao-tools/ao-view/aoview_util.c
@@ -0,0 +1,91 @@
+/*
+ * 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 "aoview.h"
+
+char *
+aoview_fullname (char *dir, char *file)
+{
+ char *new;
+ int dlen = strlen (dir);
+ int flen = strlen (file);
+ int slen = 0;
+
+ if (dir[dlen-1] != '/')
+ slen = 1;
+ new = malloc (dlen + slen + flen + 1);
+ if (!new)
+ return 0;
+ strcpy(new, dir);
+ if (slen)
+ strcat (new, "/");
+ strcat(new, file);
+ return new;
+}
+
+char *
+aoview_basename(char *file)
+{
+ char *b;
+
+ b = strrchr(file, '/');
+ if (!b)
+ return file;
+ return b + 1;
+}
+
+int
+aoview_mkdir(char *dir)
+{
+ char *slash;
+ char *d;
+ char *part;
+
+ d = dir;
+ for (;;) {
+ slash = strchr (d, '/');
+ if (!slash)
+ slash = d + strlen(d);
+ if (!*slash)
+ break;
+ part = strndup(dir, slash - dir);
+ if (!access(part, F_OK))
+ if (mkdir(part, 0777) < 0)
+ return -errno;
+ free(part);
+ d = slash + 1;
+ }
+ return 0;
+}
+
+GtkTreeViewColumn *
+aoview_add_plain_text_column (GtkTreeView *view, const gchar *title, gint model_column, gint width)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL);
+ g_object_set(renderer, "width-chars", width, NULL);
+ column = gtk_tree_view_column_new_with_attributes (title, renderer,
+ "text", model_column,
+ NULL);
+ gtk_tree_view_column_set_resizable (column, FALSE);
+ gtk_tree_view_append_column (view, column);
+
+ return column;
+}
diff --git a/ao-tools/ao-view/aoview_voice.c b/ao-tools/ao-view/aoview_voice.c
new file mode 100644
index 00000000..24422df6
--- /dev/null
+++ b/ao-tools/ao-view/aoview_voice.c
@@ -0,0 +1,122 @@
+/*
+ * 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 "aoview.h"
+
+#if HAVE_FLITE
+#include <stdarg.h>
+
+FILE *aoview_flite;
+
+void aoview_voice_open(void)
+{
+ int err;
+
+ if (!aoview_flite)
+ aoview_flite = aoview_flite_start();
+}
+
+void aoview_voice_close(void)
+{
+ if (aoview_flite) {
+ aoview_flite_stop();
+ aoview_flite = NULL;
+ }
+}
+
+void aoview_voice_speak(char *format, ...)
+{
+ va_list ap;
+
+ if (aoview_flite) {
+ va_start(ap, format);
+ vfprintf(aoview_flite, format, ap);
+ fflush(aoview_flite);
+ va_end(ap);
+ }
+}
+
+#else
+void aoview_voice_open(void)
+{
+}
+
+void aoview_voice_close(void)
+{
+}
+
+void aoview_voice_speak(char *format, ...)
+{
+}
+#endif
+
+
+static GtkCheckMenuItem *voice_enable;
+
+#define ALTOS_VOICE_PATH "/apps/aoview/voice"
+
+static void
+aoview_voice_enable(GtkWidget *widget, gpointer data)
+{
+ gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
+ GError *error;
+ GConfClient *gconf_client;
+
+ if (enabled) {
+ aoview_voice_open();
+ aoview_voice_speak("enable voice\n");
+ } else {
+ aoview_voice_speak("disable voice\n");
+ aoview_voice_close();
+ }
+ gconf_client = gconf_client_get_default();
+ gconf_client_set_bool(gconf_client,
+ ALTOS_VOICE_PATH,
+ enabled,
+ &error);
+}
+
+void
+aoview_voice_init(GladeXML *xml)
+{
+ gboolean enabled;
+ GConfClient *gconf_client;
+
+ voice_enable = GTK_CHECK_MENU_ITEM(glade_xml_get_widget(xml, "voice_enable"));
+ assert(voice_enable);
+
+ gconf_client = gconf_client_get_default();
+ enabled = TRUE;
+ if (gconf_client)
+ {
+ GError *error;
+
+ error = NULL;
+ enabled = gconf_client_get_bool(gconf_client,
+ ALTOS_VOICE_PATH,
+ &error);
+ if (error)
+ enabled = TRUE;
+ }
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(voice_enable), enabled);
+ if (enabled)
+ aoview_voice_open();
+
+ g_signal_connect(G_OBJECT(voice_enable), "toggled",
+ G_CALLBACK(aoview_voice_enable),
+ voice_enable);
+}
diff --git a/ao-tools/ao-view/design b/ao-tools/ao-view/design
new file mode 100644
index 00000000..6ec2ea70
--- /dev/null
+++ b/ao-tools/ao-view/design
@@ -0,0 +1,27 @@
+Requirements:
+ real-time display of telemetry
+ off-line display of logged data
+ Logging of telemetry
+ Capture of logged data to disk
+
+Input data:
+ accelerometer
+ barometer
+ thermometer
+ gps
+ drogue and main continuity
+ battery voltage
+ time
+ reported flight state
+ reported events
+
+Computed data:
+ velocity (from accelerometer)
+ altitude
+ range
+ direction
+
+Displays:
+ numeric display of current rocket status
+ (graphics come later)
+ text message log
diff --git a/ao-tools/lib/Makefile.am b/ao-tools/lib/Makefile.am
index 9584e216..1f8f2e42 100644
--- a/ao-tools/lib/Makefile.am
+++ b/ao-tools/lib/Makefile.am
@@ -1,6 +1,9 @@
noinst_LIBRARIES = libao-tools.a
-AM_CFLAGS=$(WARN_CFLAGS) $(LIBUSB_CFLAGS)
+AM_CFLAGS=$(WARN_CFLAGS) $(LIBUSB_CFLAGS) $(GNOME_CFLAGS)
+
+libao_tools_a_uneeded = \
+ cc-log.c
libao_tools_a_SOURCES = \
ccdbg-command.c \
@@ -14,9 +17,26 @@ libao_tools_a_SOURCES = \
ccdbg-memory.c \
ccdbg-rom.c \
ccdbg-state.c \
+ cc-analyse.c \
+ cc-convert.c \
+ cc-dsp.c \
+ cc-integrate.c \
+ cc-period.c \
+ cc-process.c \
cc-usb.c \
cc-usb.h \
+ cc.h \
+ cc-usbdev.c \
+ cc-util.c \
cc-bitbang.c \
cc-bitbang.h \
+ cc-logfile.c \
+ cc-telem.c \
+ cc-telemetry.c \
+ cc-telemetry.h \
cp-usb-async.c \
- cp-usb-async.h
+ cp-usb-async.h \
+ i0.c \
+ chbevl.c \
+ mconf.h \
+ cephes.h
diff --git a/ao-tools/lib/cc-analyse.c b/ao-tools/lib/cc-analyse.c
new file mode 100644
index 00000000..27c416a6
--- /dev/null
+++ b/ao-tools/lib/cc-analyse.c
@@ -0,0 +1,267 @@
+/*
+ * 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>
+
+void
+cc_timedata_limits(struct cc_timedata *d, double min_time, double max_time, int *start, int *stop)
+{
+ int i;
+
+ *start = -1;
+ for (i = 0; i < d->num; i++) {
+ if (*start < 0 && min_time <= d->data[i].time)
+ *start = i;
+ if (d->data[i].time <= max_time)
+ *stop = i;
+ }
+}
+
+int
+cc_timedata_min(struct cc_timedata *d, double min_time, double max_time)
+{
+ int i;
+ int set = 0;
+ int min_i = -1;
+ double min;
+
+ if (d->num == 0)
+ return -1;
+ 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_min_mag(struct cc_timedata *d, double min_time, double max_time)
+{
+ int i;
+ int set = 0;
+ int min_i = -1;
+ double min;
+
+ if (d->num == 0)
+ return -1;
+ for (i = 0; i < d->num; i++)
+ if (min_time <= d->data[i].time && d->data[i].time <= max_time)
+ if (!set || fabs(d->data[i].value) < min) {
+ min_i = i;
+ min = fabs(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 = -1;
+ int set = 0;
+
+ if (d->num == 0)
+ return -1;
+ 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;
+}
+
+int
+cc_timedata_max_mag(struct cc_timedata *d, double min_time, double max_time)
+{
+ int i;
+ double max;
+ int max_i = -1;
+ int set = 0;
+
+ if (d->num == 0)
+ return -1;
+ for (i = 0; i < d->num; i++)
+ if (min_time <= d->data[i].time && d->data[i].time <= max_time)
+ if (!set || fabs(d->data[i].value) > max) {
+ max_i = i;
+ max = fabs(d->data[i].value);
+ set = 1;
+ }
+ return max_i;
+}
+
+double
+cc_timedata_average(struct cc_timedata *td, double start_time, double stop_time)
+{
+ int i;
+ double prev_time;
+ double next_time;
+ double interval;
+ double sum = 0.0;
+ double period = 0.0;
+
+ prev_time = start_time;
+ for (i = 0; i < td->num; i++) {
+ if (start_time <= td->data[i].time && td->data[i].time <= stop_time) {
+ if (i < td->num - 1 && td->data[i+1].time < stop_time)
+ next_time = (td->data[i].time + td->data[i+1].time) / 2.0;
+ else
+ next_time = stop_time;
+ interval = next_time - prev_time;
+ sum += td->data[i].value * interval;
+ period += interval;
+ prev_time = next_time;
+ }
+ }
+ return sum / period;
+}
+
+int
+cc_perioddata_limits(struct cc_perioddata *d, double min_time, double max_time, int *start, int *stop)
+{
+ double start_d, stop_d;
+
+ if (d->num == 0)
+ return 0;
+ start_d = ceil((min_time - d->start) / d->step);
+ if (start_d < 0)
+ start_d = 0;
+ stop_d = floor((max_time - d->start) / d->step);
+ if (stop_d >= d->num)
+ stop_d = d->num - 1;
+ if (stop_d < start_d)
+ return 0;
+ *start = (int) start_d;
+ *stop = (int) stop_d;
+ return 1;
+}
+
+int
+cc_perioddata_min(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int i;
+ double min;
+ int min_i;
+ int start, stop;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return -1;
+ min = d->data[start];
+ min_i = start;
+ for (i = start + 1; i <= stop; i++)
+ if (d->data[i] < min) {
+ min = d->data[i];
+ min_i = i;
+ }
+ return min_i;
+}
+
+int
+cc_perioddata_min_mag(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int start, stop;
+ int i;
+ double min;
+ int min_i;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return -1;
+ min = d->data[start];
+ min_i = start;
+ for (i = start + 1; i <= stop; i++)
+ if (fabs(d->data[i]) < min) {
+ min = fabs(d->data[i]);
+ min_i = i;
+ }
+ return min_i;
+}
+
+int
+cc_perioddata_max(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int start, stop;
+ int i;
+ double max;
+ int max_i;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return -1;
+ max = d->data[start];
+ max_i = start;
+ for (i = start + 1; i <= stop; i++)
+ if (d->data[i] > max) {
+ max = d->data[i];
+ max_i = i;
+ }
+ return max_i;
+}
+
+int
+cc_perioddata_max_mag(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int start, stop;
+ int i;
+ double max;
+ int max_i;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return -1;
+ max = d->data[start];
+ max_i = start;
+ for (i = start + 1; i <= stop; i++)
+ if (fabs(d->data[i]) > max) {
+ max = fabs(d->data[i]);
+ max_i = i;
+ }
+ return max_i;
+}
+
+double
+cc_perioddata_average(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int start, stop;
+ int i;
+ double sum = 0.0;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return 0.0;
+ for (i = start; i <= stop; i++)
+ sum += d->data[i];
+ return sum / (stop - start + 1);
+}
+
+double
+cc_perioddata_average_mag(struct cc_perioddata *d, double min_time, double max_time)
+{
+ int start, stop;
+ int i;
+ double sum = 0.0;
+
+ if (!cc_perioddata_limits(d, min_time, max_time, &start, &stop))
+ return 0.0;
+ for (i = start; i <= stop; i++)
+ sum += fabs(d->data[i]);
+ return sum / (stop - start + 1);
+}
diff --git a/ao-tools/lib/cc-convert.c b/ao-tools/lib/cc-convert.c
new file mode 100644
index 00000000..8d6876a0
--- /dev/null
+++ b/ao-tools/lib/cc-convert.c
@@ -0,0 +1,284 @@
+/*
+ * 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;
+}
+
+/* Value for the CC1111 built-in temperature sensor
+ * Output voltage at 0°C = 0.755V
+ * Coefficient = 0.00247V/°C
+ * Reference voltage = 1.25V
+ *
+ * temp = ((value / 32767) * 1.25 - 0.755) / 0.00247
+ * = (value - 19791.268) / 32768 * 1.25 / 0.00247
+ */
+
+double
+cc_thermometer_to_temperature(double thermo)
+{
+ return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247;
+}
+
+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-dsp.c b/ao-tools/lib/cc-dsp.c
new file mode 100644
index 00000000..518c1a68
--- /dev/null
+++ b/ao-tools/lib/cc-dsp.c
@@ -0,0 +1,145 @@
+/*
+ * 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 "cephes.h"
+#include <math.h>
+#include <stdlib.h>
+
+static inline double sqr (double x) { return x * x; }
+
+/*
+ * Kaiser Window digital filter
+ */
+
+#if 0
+/* not used in this program */
+static double highpass (double n, double m, double wc)
+{
+ double alpha = m/2;
+ double dist;
+
+ dist = n - alpha;
+ if (dist == 0)
+ return (M_PI/2 - wc) / M_PI;
+ return -sin(dist * (M_PI/2-wc)) / (M_PI * dist);
+}
+#endif
+
+static double lowpass (double n, double m, double wc)
+{
+ double alpha = m/2;
+ double dist;
+ dist = n - alpha;
+ if (dist == 0)
+ return wc / M_PI;
+ return sin (wc * dist) / (M_PI * dist);
+}
+
+static double kaiser (double n, double m, double beta)
+{
+ double alpha = m / 2;
+ return i0 (beta * sqrt (1 - sqr((n - alpha) / alpha))) / i0(beta);
+}
+
+static double beta (double A)
+{
+ if (A > 50)
+ return 0.1102 * (A - 8.7);
+ else if (A >= 21)
+ return 0.5842 * pow((A - 21), 0.4) + 0.07886 * (A - 21);
+ else
+ return 0.0;
+}
+
+static int M (double A, double delta_omega)
+{
+ if (A > 21)
+ return ceil ((A - 7.95) / (2.285 * delta_omega));
+ else
+ return ceil(5.79 / delta_omega);
+}
+
+struct filter_param {
+ double omega_pass;
+ double delta_omega;
+ double A;
+ double beta;
+ int M;
+} filter_param_t;
+
+static struct filter_param
+filter (double omega_pass, double omega_stop, double error)
+{
+ struct filter_param p;
+ p.omega_pass = omega_pass;
+ p.delta_omega = omega_stop - omega_pass;
+ p.A = -20 * log10 (error);
+ p.beta = beta (p.A);
+ p.M = M (p.A, p.delta_omega);
+ if ((p.M & 1) == 1)
+ p.M++;
+ return p;
+}
+
+static double *
+make_low_pass_filter(double omega_pass, double omega_stop, double error, int *length_p)
+{
+ struct filter_param p = filter(omega_pass, omega_stop, error);
+ int length;
+ int n;
+ double *lpf;
+
+ length = p.M + 1;
+ lpf = calloc (length, sizeof(double));
+ for (n = 0; n < length; n++)
+ lpf[n] = lowpass(n, p.M, omega_pass) * kaiser(n, p.M, p.beta);
+ *length_p = length;
+ return lpf;
+}
+
+static double *
+convolve(double *d, int d_len, double *e, int e_len)
+{
+ int w = (e_len - 1) / 2;
+ int n;
+ double *con = calloc (d_len, sizeof (double));
+
+ for (n = 0; n < d_len; n++) {
+ double v = 0;
+ int o;
+ for (o = -w; o <= w; o++) {
+ int p = n + o;
+ double sample = p < 0 ? d[0] : p >= d_len ? d[d_len-1] : d[p];
+ v += sample * e[o + w];
+ }
+ con[n] = v;
+ }
+ return con;
+}
+
+double *
+cc_low_pass(double *data, int data_len, double omega_pass, double omega_stop, double error)
+{
+ int fir_len;
+ double *fir = make_low_pass_filter(omega_pass, omega_stop, error, &fir_len);
+ double *result;
+
+ result = convolve(data, data_len, fir, fir_len);
+ free(fir);
+ return result;
+}
diff --git a/ao-tools/lib/cc-integrate.c b/ao-tools/lib/cc-integrate.c
new file mode 100644
index 00000000..ba50761b
--- /dev/null
+++ b/ao-tools/lib/cc-integrate.c
@@ -0,0 +1,81 @@
+/*
+ * 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 <stdlib.h>
+
+struct cc_timedata *
+cc_timedata_convert(struct cc_timedata *d, double (*f)(double v, double a), double a)
+{
+ struct cc_timedata *r;
+ int n;
+
+ r = calloc (1, sizeof (struct cc_timedata));
+ r->num = d->num;
+ r->size = d->num;
+ r->data = calloc (r->size, sizeof (struct cc_timedataelt));
+ r->time_offset = d->time_offset;
+ for (n = 0; n < d->num; n++) {
+ r->data[n].time = d->data[n].time;
+ r->data[n].value = f(d->data[n].value, a);
+ }
+ return r;
+}
+
+struct cc_timedata *
+cc_timedata_integrate(struct cc_timedata *d, double min_time, double max_time)
+{
+ struct cc_timedata *i;
+ int n, m;
+ int start, stop;
+
+ cc_timedata_limits(d, min_time, max_time, &start, &stop);
+ i = calloc (1, sizeof (struct cc_timedata));
+ i->num = stop - start + 1;
+ i->size = i->num;
+ i->data = calloc (i->size, sizeof (struct cc_timedataelt));
+ i->time_offset = d->data[start].time;
+ for (n = 0; n < i->num; n++) {
+ m = n + start;
+ i->data[n].time = d->data[m].time;
+ if (n == 0) {
+ i->data[n].value = 0;
+ } else {
+ i->data[n].value = i->data[n-1].value +
+ (d->data[m].value + d->data[m-1].value) / 2 *
+ ((d->data[m].time - d->data[m-1].time) / 100.0);
+ }
+ }
+ return i;
+}
+
+struct cc_perioddata *
+cc_perioddata_differentiate(struct cc_perioddata *i)
+{
+ struct cc_perioddata *d;
+ int n;
+
+ d = calloc (1, sizeof (struct cc_perioddata));
+ d->num = i->num;
+ d->start = i->start;
+ d->step = i->step;
+ d->data = calloc (d->num, sizeof(double));
+ for (n = 1; n < d->num; n++)
+ d->data[n] = (i->data[n] - i->data[n-1]) / (i->step / 100.0);
+ d->data[0] = d->data[1];
+ return d;
+}
diff --git a/ao-tools/lib/cc-log.c b/ao-tools/lib/cc-log.c
new file mode 100644
index 00000000..ed51f87e
--- /dev/null
+++ b/ao-tools/lib/cc-log.c
@@ -0,0 +1,121 @@
+/*
+ * 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, int flight, char *ext)
+{
+ char base[50];
+ char seq[20];
+ 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 (;;) {
+ if (sequence)
+ snprintf(seq, sizeof(seq), "-seq-%03d", sequence);
+ else
+ seq[0] = '\0';
+
+ snprintf(base, sizeof (base), "%04d-%02d-%02d-serial-%03d-flight-%03d%s.%s",
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ serial,
+ flight,
+ seq,
+ 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
index 00000000..842e5c7c
--- /dev/null
+++ b/ao-tools/lib/cc-logfile.c
@@ -0,0 +1,336 @@
+/*
+ * 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>
+#include <string.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;
+ }
+ time += data->time_offset;
+ if (data->num && data->data[data->num-1].time > time) {
+ data->time_offset += 65536;
+ 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;
+ }
+ elt->time += data->time_offset;
+ if (data->num && data->data[data->num-1].time > elt->time) {
+ data->time_offset += 65536;
+ elt->time += 65536;
+ }
+ data->data[data->num] = *elt;
+ data->num++;
+ return 1;
+}
+
+static int
+gpssat_add(struct cc_gpsdata *data, struct cc_gpssat *sat)
+{
+ int i, j;
+ int reuse = 0;
+ int newsizesats;
+ struct cc_gpssats *newsats;
+
+ for (i = data->numsats; --i >= 0;) {
+ if (data->sats[i].sat[0].time == sat->time) {
+ reuse = 1;
+ break;
+ }
+ if (data->sats[i].sat[0].time < sat->time)
+ break;
+ }
+ if (!reuse) {
+ if (data->numsats == data->sizesats) {
+ if (data->sizesats == 0)
+ newsats = malloc((newsizesats = 256) * sizeof (struct cc_gpssats));
+ else
+ newsats = realloc (data->sats, (newsizesats = data->sizesats * 2)
+ * sizeof (struct cc_gpssats));
+ if (!newsats)
+ return 0;
+ data->sats = newsats;
+ data->sizesats = newsizesats;
+ }
+ i = data->numsats++;
+ data->sats[i].nsat = 0;
+ }
+ j = data->sats[i].nsat;
+ if (j < 12) {
+ data->sats[i].sat[j] = *sat;
+ data->sats[i].nsat = j + 1;
+ }
+ 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_GPS_DATE 'Y'
+
+#define AO_LOG_POS_NONE (~0UL)
+
+#define GPS_TIME 1
+#define GPS_LAT 2
+#define GPS_LON 4
+#define GPS_ALT 8
+
+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;
+ static struct cc_gpselt gps;
+ static int gps_valid;
+ struct cc_gpssat sat;
+ 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:
+ /* the flight computer writes TIME first, so reset
+ * any stale data before adding this record
+ */
+ gps.time = tick;
+ gps.hour = (a & 0xff);
+ gps.minute = (a >> 8) & 0xff;
+ gps.second = (b & 0xff);
+ gps.flags = (b >> 8) & 0xff;
+ gps_valid = GPS_TIME;
+ break;
+ case AO_LOG_GPS_LAT:
+ gps.lat = ((int32_t) (a + (b << 16))) / 10000000.0;
+ gps_valid |= GPS_LAT;
+ break;
+ case AO_LOG_GPS_LON:
+ gps.lon = ((int32_t) (a + (b << 16))) / 10000000.0;
+ gps_valid |= GPS_LON;
+ break;
+ case AO_LOG_GPS_ALT:
+ gps.alt = (int16_t) a;
+ gps_valid |= GPS_ALT;
+ break;
+ case AO_LOG_GPS_SAT:
+ sat.time = tick;
+ sat.svid = a;
+ sat.c_n = (b >> 8) & 0xff;
+ gpssat_add(&f->gps, &sat);
+ break;
+ case AO_LOG_GPS_DATE:
+ f->year = 2000 + (a & 0xff);
+ f->month = (a >> 8) & 0xff;
+ f->day = (b & 0xff);
+ break;
+ default:
+ return 0;
+ }
+ if (gps_valid == 0xf) {
+ gps_valid = 0;
+ gpsdata_add(&f->gps, &gps);
+ }
+ return 1;
+}
+
+static const char *state_names[] = {
+ "startup",
+ "idle",
+ "pad",
+ "boost",
+ "fast",
+ "coast",
+ "drogue",
+ "main",
+ "landed",
+ "invalid"
+};
+
+static enum ao_flight_state
+state_name_to_state(char *state_name)
+{
+ enum ao_flight_state state;
+ for (state = ao_flight_startup; state < ao_flight_invalid; state++)
+ if (!strcmp(state_names[state], state_name))
+ return state;
+ return ao_flight_invalid;
+}
+
+static int
+read_telem(const char *line, struct cc_flightraw *f)
+{
+ struct cc_telem telem;
+ struct cc_gpselt gps;
+ struct cc_gpssat sat;
+ int s;
+
+ 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);
+ timedata_add(&f->state, telem.tick, state_name_to_state(telem.state));
+ if (telem.gps.gps_locked) {
+ f->year = telem.gps.gps_time.year;
+ f->month = telem.gps.gps_time.month;
+ f->day = telem.gps.gps_time.day;
+ gps.time = telem.tick;
+ gps.lat = telem.gps.lat;
+ gps.lon = telem.gps.lon;
+ gps.alt = telem.gps.alt;
+ gps.hour = telem.gps.gps_time.hour;
+ gps.minute = telem.gps.gps_time.minute;
+ gps.second = telem.gps.gps_time.second;
+ gpsdata_add(&f->gps, &gps);
+ }
+ for (s = 0; s < telem.gps_tracking.channels; s++) {
+ sat.time = telem.tick;
+ sat.svid = telem.gps_tracking.sats[s].svid;
+ sat.c_n = telem.gps_tracking.sats[s].c_n0;
+ gpssat_add(&f->gps, &sat);
+ }
+ 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-period.c b/ao-tools/lib/cc-period.c
new file mode 100644
index 00000000..844ac79e
--- /dev/null
+++ b/ao-tools/lib/cc-period.c
@@ -0,0 +1,56 @@
+/*
+ * 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 <stdlib.h>
+#include <math.h>
+
+struct cc_perioddata *
+cc_period_make(struct cc_timedata *td, double start_time, double stop_time)
+{
+ int len = stop_time - start_time + 1;
+ struct cc_perioddata *pd;
+ int i, j;
+ double t;
+
+ pd = calloc(1, sizeof (struct cc_perioddata));
+ pd->start = start_time;
+ pd->step = 1;
+ pd->num = len;
+ pd->data = calloc(len, sizeof(double));
+ j = 0;
+ for (i = 0; i < pd->num; i++) {
+ t = start_time + i * pd->step;
+ while (j < td->num - 1 && fabs(t - td->data[j].time) >= fabs(t - td->data[j+1].time))
+ j++;
+ pd->data[i] = td->data[j].value;
+ }
+ return pd;
+}
+
+struct cc_perioddata *
+cc_period_low_pass(struct cc_perioddata *raw, double omega_pass, double omega_stop, double error)
+{
+ struct cc_perioddata *filtered;
+
+ filtered = calloc (1, sizeof (struct cc_perioddata));
+ filtered->start = raw->start;
+ filtered->step = raw->step;
+ filtered->num = raw->num;
+ filtered->data = cc_low_pass(raw->data, raw->num, omega_pass, omega_stop, error);
+ return filtered;
+}
diff --git a/ao-tools/lib/cc-process.c b/ao-tools/lib/cc-process.c
new file mode 100644
index 00000000..c756b570
--- /dev/null
+++ b/ao-tools/lib/cc-process.c
@@ -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; 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 <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+static void
+cook_timed(struct cc_timedata *td, struct cc_perioddata *pd,
+ double start_time, double stop_time,
+ double omega_pass, double omega_stop, double error)
+{
+ struct cc_perioddata *unfiltered, *filtered;
+
+ unfiltered = cc_period_make(td, start_time, stop_time);
+ filtered = cc_period_low_pass (unfiltered, omega_pass, omega_stop, error);
+ *pd = *filtered;
+ free (filtered);
+ free (unfiltered->data);
+ free (unfiltered);
+}
+
+static double
+barometer_to_altitude(double b, double pad_alt)
+{
+ return cc_barometer_to_altitude(b) - pad_alt;
+}
+
+struct cc_flightcooked *
+cc_flight_cook(struct cc_flightraw *raw)
+{
+ struct cc_flightcooked *cooked;
+ double flight_start;
+ double flight_stop;
+ int start_set = 0;
+ int stop_set = 0;
+ int i;
+ struct cc_timedata *accel;
+ struct cc_timedata *accel_speed;
+ struct cc_timedata *accel_pos;
+ struct cc_timedata *pres;
+ struct cc_perioddata *pres_speed;
+ struct cc_perioddata *pres_accel;
+
+ if (raw->accel.num == 0)
+ return NULL;
+
+ cooked = calloc (1, sizeof (struct cc_flightcooked));
+
+ /*
+ * Find flight start and stop times by looking at
+ * state transitions. The stop time is set to the time
+ * of landing, which may be long after it landed (due to radio
+ * issues). Refine this value by looking through the sensor data
+ */
+ for (i = 0; i < raw->state.num; i++) {
+ if (!start_set && raw->state.data[i].value > ao_flight_pad) {
+ flight_start = raw->state.data[i].time - 10;
+ start_set = 1;
+ }
+ if (!stop_set && raw->state.data[i].value > ao_flight_main) {
+ flight_stop = raw->state.data[i].time;
+ stop_set = 1;
+ }
+ }
+
+ if (!start_set || flight_start < raw->accel.data[0].time)
+ flight_start = raw->accel.data[0].time;
+ if (stop_set) {
+ for (i = 0; i < raw->accel.num - 1; i++) {
+ if (raw->accel.data[i+1].time >= flight_stop) {
+ flight_stop = raw->accel.data[i].time;
+ break;
+ }
+ }
+ } else {
+ flight_stop = raw->accel.data[raw->accel.num-1].time;
+ }
+ cooked->flight_start = flight_start;
+ cooked->flight_stop = flight_stop;
+
+ /* Integrate the accelerometer data to get speed and position */
+ accel = cc_timedata_convert(&raw->accel, cc_accelerometer_to_acceleration, raw->ground_accel);
+ cooked->accel = *accel;
+ free(accel);
+ accel_speed = cc_timedata_integrate(&cooked->accel, flight_start - 10, flight_stop);
+ accel_pos = cc_timedata_integrate(accel_speed, flight_start - 10, flight_stop);
+
+#define ACCEL_OMEGA_PASS (2 * M_PI * 20 / 100)
+#define ACCEL_OMEGA_STOP (2 * M_PI * 30 / 100)
+#define BARO_OMEGA_PASS (2 * M_PI * .5 / 100)
+#define BARO_OMEGA_STOP (2 * M_PI * 1 / 100)
+#define FILTER_ERROR (1e-8)
+
+ cook_timed(&cooked->accel, &cooked->accel_accel,
+ flight_start, flight_stop,
+ ACCEL_OMEGA_PASS, ACCEL_OMEGA_STOP, FILTER_ERROR);
+ cook_timed(accel_speed, &cooked->accel_speed,
+ flight_start, flight_stop,
+ ACCEL_OMEGA_PASS, ACCEL_OMEGA_STOP, FILTER_ERROR);
+ free(accel_speed->data); free(accel_speed);
+ cook_timed(accel_pos, &cooked->accel_pos,
+ flight_start, flight_stop,
+ ACCEL_OMEGA_PASS, ACCEL_OMEGA_STOP, FILTER_ERROR);
+ free(accel_pos->data); free(accel_pos);
+
+ /* Filter the pressure data */
+ pres = cc_timedata_convert(&raw->pres, barometer_to_altitude,
+ cc_barometer_to_altitude(raw->ground_pres));
+ cooked->pres = *pres;
+ free(pres);
+ cook_timed(&cooked->pres, &cooked->pres_pos,
+ flight_start, flight_stop,
+ BARO_OMEGA_PASS, BARO_OMEGA_STOP, FILTER_ERROR);
+ /* differentiate twice to get to acceleration */
+ pres_speed = cc_perioddata_differentiate(&cooked->pres_pos);
+ pres_accel = cc_perioddata_differentiate(pres_speed);
+
+ cooked->pres_speed = *pres_speed;
+ free(pres_speed);
+ cooked->pres_accel = *pres_accel;
+ free(pres_accel);
+
+ /* copy state */
+ cooked->state.num = raw->state.num;
+ cooked->state.size = raw->state.num;
+ cooked->state.data = calloc(cooked->state.num, sizeof (struct cc_timedataelt));
+ memcpy(cooked->state.data, raw->state.data, cooked->state.num * sizeof (struct cc_timedataelt));
+ cooked->state.time_offset = raw->state.time_offset;
+ return cooked;
+}
+
+#define if_free(x) ((x) ? free(x) : (void) 0)
+
+void
+cc_flightcooked_free(struct cc_flightcooked *cooked)
+{
+ if_free(cooked->accel_accel.data);
+ if_free(cooked->accel_speed.data);
+ if_free(cooked->accel_pos.data);
+ if_free(cooked->pres_pos.data);
+ if_free(cooked->pres_speed.data);
+ if_free(cooked->pres_accel.data);
+ if_free(cooked->gps_lat.data);
+ if_free(cooked->gps_lon.data);
+ if_free(cooked->gps_alt.data);
+ if_free(cooked->state.data);
+ if_free(cooked->accel.data);
+ if_free(cooked->pres.data);
+ free(cooked);
+}
diff --git a/ao-tools/lib/cc-telem.c b/ao-tools/lib/cc-telem.c
new file mode 100644
index 00000000..aa52b7c5
--- /dev/null
+++ b/ao-tools/lib/cc-telem.c
@@ -0,0 +1,212 @@
+/*
+ * 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 *raw_words[PARSE_MAX_WORDS];
+ char **words;
+ int version = 0;
+ 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++) {
+ raw_words[nword] = strtok_r(line, " \t\n", &saveptr);
+ line = NULL;
+ if (raw_words[nword] == NULL)
+ break;
+ }
+ if (nword < 36)
+ return FALSE;
+ words = raw_words;
+ if (strcmp(words[0], "VERSION") == 0) {
+ cc_parse_int(&version, words[1]);
+ words += 2;
+ nword -= 2;
+ }
+
+ 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]);
+
+ if (version >= 2) {
+ cc_parse_int(&telem->flight, words[5]);
+ words += 2;
+ nword -= 2;
+ } else
+ telem->flight = 0;
+
+ cc_parse_int(&telem->rssi, words[5]);
+ if (version <= 2) {
+ /* Older telemetry versions mis-computed the rssi value */
+ telem->rssi = (telem->rssi + 74) / 2 - 74;
+ }
+ 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]);
+ if (version >= 1) {
+ cc_parse_int(&telem->accel_plus_g, words[34]);
+ cc_parse_int(&telem->accel_minus_g, words[36]);
+ words += 4;
+ nword -= 4;
+ } else {
+ telem->accel_plus_g = telem->ground_accel;
+ telem->accel_minus_g = telem->ground_accel + 530;
+ }
+ 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.year = telem->gps.gps_time.month = telem->gps.gps_time.day = 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;
+ if (version >= 2) {
+ sscanf(words[36], "%d-%d-%d",
+ &telem->gps.gps_time.year,
+ &telem->gps.gps_time.month,
+ &telem->gps.gps_time.day);
+ words += 1;
+ nword -= 1;
+ } else {
+ telem->gps.gps_time.year = telem->gps.gps_time.month = telem->gps.gps_time.day = 0;
+ }
+ 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.year = telem->gps.gps_time.month = telem->gps.gps_time.day = 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;
+ int per_sat;
+ int state;
+
+ if (version >= 2)
+ per_sat = 2;
+ else
+ per_sat = 3;
+ cc_parse_int(&n, words[tracking_pos + 1]);
+ pos = tracking_pos + 2;
+ if (nword >= pos + n * per_sat) {
+ telem->gps_tracking.channels = n;
+ for (c = 0; c < n; c++) {
+ cc_parse_int(&telem->gps_tracking.sats[c].svid,
+ words[pos + 0]);
+ if (version < 2)
+ cc_parse_hex(&state, words[pos + 1]);
+ cc_parse_int(&telem->gps_tracking.sats[c].c_n0,
+ words[pos + per_sat - 1]);
+ pos += per_sat;
+ }
+ } else {
+ telem->gps_tracking.channels = 0;
+ }
+ } else {
+ telem->gps_tracking.channels = 0;
+ }
+ return TRUE;
+}
diff --git a/ao-tools/lib/cc-telemetry.c b/ao-tools/lib/cc-telemetry.c
new file mode 100644
index 00000000..99da2680
--- /dev/null
+++ b/ao-tools/lib/cc-telemetry.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2011 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>
+
+static int
+parse_byte(char *data, uint8_t *byte)
+{
+ char d[3];
+ int x;
+ d[0] = data[0];
+ d[1] = data[1];
+ d[2] = '\0';
+
+ if (sscanf(d, "%x", &x) != 1)
+ return FALSE;
+ *byte = x;
+ return TRUE;
+}
+
+int
+cc_telemetry_parse(const char *input_line, union ao_telemetry_all *telemetry)
+{
+ uint8_t *byte;
+ char *data;
+ uint8_t hex[35];
+ int i;
+
+ if (strncmp(input_line, "TELEM", 5) != 0)
+ return FALSE;
+
+ data = strchr (input_line, ' ');
+ if (!data)
+ return FALSE;
+ data++;
+ byte = hex;
+ for (i = 0; i < 35; i++) {
+ if (!parse_byte(data, byte))
+ return FALSE;
+ data += 2;
+ byte++;
+ }
+ if (hex[0] != 34)
+ return FALSE;
+ memcpy(telemetry, hex+1, 34);
+ return TRUE;
+}
diff --git a/ao-tools/lib/cc-telemetry.h b/ao-tools/lib/cc-telemetry.h
new file mode 100644
index 00000000..e849cd3b
--- /dev/null
+++ b/ao-tools/lib/cc-telemetry.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright © 2011 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.
+ */
+
+#ifndef _CC_TELEMETRY_H_
+#define _CC_TELEMETRY_H_
+/*
+ * ao_telemetry.c
+ */
+#define AO_MAX_CALLSIGN 8
+#define AO_MAX_VERSION 8
+#define AO_MAX_TELEMETRY 128
+
+struct ao_telemetry_generic {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+ uint8_t payload[27]; /* 5 */
+ uint8_t rssi; /* 32 */
+ uint8_t status; /* 33 */
+ /* 34 */
+};
+
+#define AO_TELEMETRY_SENSOR_TELEMETRUM 0x01
+#define AO_TELEMETRY_SENSOR_TELEMINI 0x02
+#define AO_TELEMETRY_SENSOR_TELENANO 0x03
+
+struct ao_telemetry_sensor {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+
+ uint8_t state; /* 5 flight state */
+ int16_t accel; /* 6 accelerometer (TM only) */
+ int16_t pres; /* 8 pressure sensor */
+ int16_t temp; /* 10 temperature sensor */
+ int16_t v_batt; /* 12 battery voltage */
+ int16_t sense_d; /* 14 drogue continuity sense (TM/Tm) */
+ int16_t sense_m; /* 16 main continuity sense (TM/Tm) */
+
+ int16_t acceleration; /* 18 m/s² * 16 */
+ int16_t speed; /* 20 m/s * 16 */
+ int16_t height; /* 22 m */
+
+ int16_t ground_pres; /* 24 average pres on pad */
+ int16_t ground_accel; /* 26 average accel on pad */
+ int16_t accel_plus_g; /* 28 accel calibration at +1g */
+ int16_t accel_minus_g; /* 30 accel calibration at -1g */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_CONFIGURATION 0x04
+
+struct ao_telemetry_configuration {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+
+ uint8_t device; /* 5 device type */
+ uint16_t flight; /* 6 flight number */
+ uint8_t config_major; /* 8 Config major version */
+ uint8_t config_minor; /* 9 Config minor version */
+ uint16_t apogee_delay; /* 10 Apogee deploy delay in seconds */
+ uint16_t main_deploy; /* 12 Main deploy alt in meters */
+ uint16_t flight_log_max; /* 14 Maximum flight log size in kB */
+ char callsign[AO_MAX_CALLSIGN]; /* 16 Radio operator identity */
+ char version[AO_MAX_VERSION]; /* 24 Software version */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_LOCATION 0x05
+
+#define AO_GPS_MODE_NOT_VALID 'N'
+#define AO_GPS_MODE_AUTONOMOUS 'A'
+#define AO_GPS_MODE_DIFFERENTIAL 'D'
+#define AO_GPS_MODE_ESTIMATED 'E'
+#define AO_GPS_MODE_MANUAL 'M'
+#define AO_GPS_MODE_SIMULATED 'S'
+
+struct ao_telemetry_location {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+
+ uint8_t flags; /* 5 Number of sats and other flags */
+ int16_t altitude; /* 6 GPS reported altitude (m) */
+ int32_t latitude; /* 8 latitude (degrees * 10⁷) */
+ int32_t longitude; /* 12 longitude (degrees * 10⁷) */
+ uint8_t year; /* 16 (- 2000) */
+ uint8_t month; /* 17 (1-12) */
+ uint8_t day; /* 18 (1-31) */
+ uint8_t hour; /* 19 (0-23) */
+ uint8_t minute; /* 20 (0-59) */
+ uint8_t second; /* 21 (0-59) */
+ uint8_t pdop; /* 22 (m * 5) */
+ uint8_t hdop; /* 23 (m * 5) */
+ uint8_t vdop; /* 24 (m * 5) */
+ uint8_t mode; /* 25 */
+ uint16_t ground_speed; /* 26 cm/s */
+ int16_t climb_rate; /* 28 cm/s */
+ uint8_t course; /* 30 degrees / 2 */
+ uint8_t unused[1]; /* 31 */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_SATELLITE 0x06
+
+struct ao_telemetry_satellite_info {
+ uint8_t svid;
+ uint8_t c_n_1;
+};
+
+struct ao_telemetry_satellite {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+ uint8_t channels; /* 5 number of reported sats */
+
+ struct ao_telemetry_satellite_info sats[12]; /* 6 */
+ uint8_t unused[2]; /* 30 */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_COMPANION 0x07
+
+#define AO_COMPANION_MAX_CHANNELS 12
+
+struct ao_telemetry_companion {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+ uint8_t board_id; /* 5 */
+
+ uint8_t update_period; /* 6 */
+ uint8_t channels; /* 7 */
+ uint16_t companion_data[AO_COMPANION_MAX_CHANNELS]; /* 8 */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_MEGA_SENSOR 0x08
+
+struct ao_telemetry_mega_sensor {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+
+ uint8_t pad5; /* 5 */
+ int16_t accel; /* 6 Z axis */
+
+ int32_t pres; /* 8 Pa * 10 */
+ int16_t temp; /* 12 °C * 100 */
+
+ int16_t accel_x; /* 14 */
+ int16_t accel_y; /* 16 */
+ int16_t accel_z; /* 18 */
+
+ int16_t gyro_x; /* 20 */
+ int16_t gyro_y; /* 22 */
+ int16_t gyro_z; /* 24 */
+
+ int16_t mag_x; /* 26 */
+ int16_t mag_y; /* 28 */
+ int16_t mag_z; /* 30 */
+ /* 32 */
+};
+
+#define AO_TELEMETRY_MEGA_DATA 0x09
+
+struct ao_telemetry_mega_data {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+
+ uint8_t state; /* 5 flight state */
+
+ int16_t v_batt; /* 6 battery voltage */
+ int16_t v_pyro; /* 8 pyro battery voltage */
+ int8_t sense[6]; /* 10 continuity sense */
+
+ int32_t ground_pres; /* 16 average pres on pad */
+ int16_t ground_accel; /* 20 average accel on pad */
+ int16_t accel_plus_g; /* 22 accel calibration at +1g */
+ int16_t accel_minus_g; /* 24 accel calibration at -1g */
+
+ int16_t acceleration; /* 26 m/s² * 16 */
+ int16_t speed; /* 28 m/s * 16 */
+ int16_t height; /* 30 m */
+ /* 32 */
+};
+
+/* #define AO_SEND_ALL_BARO */
+
+#define AO_TELEMETRY_BARO 0x80
+
+/*
+ * This packet allows the full sampling rate baro
+ * data to be captured over the RF link so that the
+ * flight software can be tested using 'real' data.
+ *
+ * Along with this telemetry packet, the flight
+ * code is modified to send full-rate telemetry all the time
+ * and never send an RDF tone; this ensure that the full radio
+ * link is available.
+ */
+struct ao_telemetry_baro {
+ uint16_t serial; /* 0 */
+ uint16_t tick; /* 2 */
+ uint8_t type; /* 4 */
+ uint8_t samples; /* 5 number samples */
+
+ int16_t baro[12]; /* 6 samples */
+ /* 32 */
+};
+
+union ao_telemetry_all {
+ struct ao_telemetry_generic generic;
+ struct ao_telemetry_sensor sensor;
+ struct ao_telemetry_configuration configuration;
+ struct ao_telemetry_location location;
+ struct ao_telemetry_satellite satellite;
+ struct ao_telemetry_companion companion;
+ struct ao_telemetry_mega_sensor mega_sensor;
+ struct ao_telemetry_mega_data mega_data;
+ struct ao_telemetry_baro baro;
+};
+
+int
+cc_telemetry_parse(const char *input_line, union ao_telemetry_all *telemetry);
+
+#endif
diff --git a/ao-tools/lib/cc-usb.c b/ao-tools/lib/cc-usb.c
index 81309983..1580c6d9 100644
--- a/ao-tools/lib/cc-usb.c
+++ b/ao-tools/lib/cc-usb.c
@@ -30,27 +30,31 @@
#include "cc-usb.h"
-#define CC_NUM_READ 16
+#define CC_NUM_HEX_READ 64
/*
* AltOS has different buffer sizes for in/out packets
*/
-#define CC_IN_BUF 256
+#define CC_IN_BUF 65536
#define CC_OUT_BUF 64
#define DEFAULT_TTY "/dev/ttyACM0"
-struct cc_read {
+struct cc_hex_read {
uint8_t *buf;
int len;
};
struct cc_usb {
- int fd;
- uint8_t in_buf[CC_IN_BUF];
- int in_count;
- uint8_t out_buf[CC_OUT_BUF];
- int out_count;
- struct cc_read read_buf[CC_NUM_READ];
- int read_count;
+ int fd;
+ uint8_t in_buf[CC_IN_BUF];
+ int in_pos;
+ int in_count;
+ uint8_t out_buf[CC_OUT_BUF];
+ int out_count;
+
+ struct cc_hex_read hex_buf[CC_NUM_HEX_READ];
+ int hex_count;
+
+ int remote;
};
#define NOT_HEX 0xff
@@ -72,61 +76,48 @@ cc_hex_nibble(uint8_t c)
* and write them to the waiting buffer
*/
static void
-cc_handle_in(struct cc_usb *cc)
+cc_handle_hex_read(struct cc_usb *cc)
{
uint8_t h, l;
- int in_pos;
- int read_pos;
+ int hex_pos;
- in_pos = 0;
- read_pos = 0;
- while (read_pos < cc->read_count && in_pos < cc->in_count) {
+ hex_pos = 0;
+ while (hex_pos < cc->hex_count && cc->in_pos < cc->in_count) {
/*
* Skip to next hex character
*/
- while (in_pos < cc->in_count &&
- cc_hex_nibble(cc->in_buf[in_pos]) == NOT_HEX)
- in_pos++;
+ while (cc->in_pos < cc->in_count &&
+ cc_hex_nibble(cc->in_buf[cc->in_pos]) == NOT_HEX)
+ cc->in_pos++;
/*
* Make sure we have two characters left
*/
- if (cc->in_count - in_pos < 2)
+ if (cc->in_count - cc->in_pos < 2)
break;
/*
* Parse hex number
*/
- h = cc_hex_nibble(cc->in_buf[in_pos]);
- l = cc_hex_nibble(cc->in_buf[in_pos+1]);
+ h = cc_hex_nibble(cc->in_buf[cc->in_pos]);
+ l = cc_hex_nibble(cc->in_buf[cc->in_pos+1]);
if (h == NOT_HEX || l == NOT_HEX) {
fprintf(stderr, "hex read error\n");
break;
}
- in_pos += 2;
+ cc->in_pos += 2;
/*
* Store hex number
*/
- *cc->read_buf[read_pos].buf++ = (h << 4) | l;
- if (--cc->read_buf[read_pos].len <= 0)
- read_pos++;
- }
-
- /* Move remaining bytes to the start of the input buffer */
- if (in_pos) {
- memmove(cc->in_buf, cc->in_buf + in_pos,
- cc->in_count - in_pos);
- cc->in_count -= in_pos;
+ *cc->hex_buf[hex_pos].buf++ = (h << 4) | l;
+ if (--cc->hex_buf[hex_pos].len <= 0)
+ hex_pos++;
}
- /* Move pending reads to the start of the array */
- if (read_pos) {
- memmove(cc->read_buf, cc->read_buf + read_pos,
- (cc->read_count - read_pos) * sizeof (cc->read_buf[0]));
- cc->read_count -= read_pos;
+ /* Move pending hex reads to the start of the array */
+ if (hex_pos) {
+ memmove(cc->hex_buf, cc->hex_buf + hex_pos,
+ (cc->hex_count - hex_pos) * sizeof (cc->hex_buf[0]));
+ cc->hex_count -= hex_pos;
}
-
- /* Once we're done reading, flush any pending input */
- if (cc->read_count == 0)
- cc->in_count = 0;
}
static void
@@ -157,8 +148,9 @@ cc_usb_dbg(int indent, uint8_t *bytes, int len)
/*
* Flush pending writes, fill pending reads
*/
-void
-cc_usb_sync(struct cc_usb *cc)
+
+static int
+_cc_usb_sync(struct cc_usb *cc, int wait_for_input)
{
int ret;
struct pollfd fds;
@@ -166,21 +158,33 @@ cc_usb_sync(struct cc_usb *cc)
fds.fd = cc->fd;
for (;;) {
- if (cc->read_count || cc->out_count)
- timeout = -1;
+ if (cc->hex_count || cc->out_count)
+ timeout = 5000;
+ else if (wait_for_input && cc->in_pos == cc->in_count)
+ timeout = wait_for_input;
else
timeout = 0;
fds.events = 0;
+ /* Move remaining bytes to the start of the input buffer */
+ if (cc->in_pos) {
+ memmove(cc->in_buf, cc->in_buf + cc->in_pos,
+ cc->in_count - cc->in_pos);
+ cc->in_count -= cc->in_pos;
+ cc->in_pos = 0;
+ }
if (cc->in_count < CC_IN_BUF)
fds.events |= POLLIN;
if (cc->out_count)
fds.events |= POLLOUT;
ret = poll(&fds, 1, timeout);
- if (ret == 0)
+ if (ret == 0) {
+ if (timeout)
+ return -1;
break;
+ }
if (ret < 0) {
perror("poll");
- break;
+ return -1;
}
if (fds.revents & POLLIN) {
ret = read(cc->fd, cc->in_buf + cc->in_count,
@@ -188,7 +192,8 @@ cc_usb_sync(struct cc_usb *cc)
if (ret > 0) {
cc_usb_dbg(24, cc->in_buf + cc->in_count, ret);
cc->in_count += ret;
- cc_handle_in(cc);
+ if (cc->hex_count)
+ cc_handle_hex_read(cc);
} else if (ret < 0)
perror("read");
}
@@ -205,6 +210,16 @@ cc_usb_sync(struct cc_usb *cc)
perror("write");
}
}
+ return 0;
+}
+
+void
+cc_usb_sync(struct cc_usb *cc)
+{
+ if (_cc_usb_sync(cc, 0) < 0) {
+ fprintf(stderr, "USB link timeout\n");
+ exit(1);
+ }
}
void
@@ -239,6 +254,38 @@ cc_usb_printf(struct cc_usb *cc, char *format, ...)
}
int
+cc_usb_getchar(struct cc_usb *cc)
+{
+ while (cc->in_pos == cc->in_count) {
+ if (_cc_usb_sync(cc, 5000) < 0) {
+ fprintf(stderr, "USB link timeout\n");
+ exit(1);
+ }
+ }
+ return cc->in_buf[cc->in_pos++];
+}
+
+void
+cc_usb_getline(struct cc_usb *cc, char *line, int max)
+{
+ int c;
+
+ while ((c = cc_usb_getchar(cc)) != '\n') {
+ switch (c) {
+ case '\r':
+ break;
+ default:
+ if (max > 1) {
+ *line++ = c;
+ max--;
+ }
+ break;
+ }
+ }
+ *line++ = '\0';
+}
+
+int
cc_usb_send_bytes(struct cc_usb *cc, uint8_t *bytes, int len)
{
int this_len;
@@ -260,12 +307,18 @@ cc_usb_send_bytes(struct cc_usb *cc, uint8_t *bytes, int len)
void
cc_queue_read(struct cc_usb *cc, uint8_t *buf, int len)
{
- struct cc_read *read_buf;
- while (cc->read_count >= CC_NUM_READ)
+ struct cc_hex_read *hex_buf;
+
+ /* At the start of a command sequence, flush any pending input */
+ if (cc->hex_count == 0) {
+ cc_usb_sync(cc);
+ cc->in_count = 0;
+ }
+ while (cc->hex_count >= CC_NUM_HEX_READ)
cc_usb_sync(cc);
- read_buf = &cc->read_buf[cc->read_count++];
- read_buf->buf = buf;
- read_buf->len = len;
+ hex_buf = &cc->hex_buf[cc->hex_count++];
+ hex_buf->buf = buf;
+ hex_buf->len = len;
}
int
@@ -321,6 +374,29 @@ cc_usb_reset(struct cc_usb *cc)
return 1;
}
+void
+cc_usb_open_remote(struct cc_usb *cc, int channel)
+{
+ if (!cc->remote) {
+ printf ("channel %d\n", channel);
+ cc_usb_printf(cc, "\nc r %d\np\nE 0\n", channel);
+ do {
+ cc->in_count = cc->in_pos = 0;
+ _cc_usb_sync(cc, 100);
+ } while (cc->in_count > 0);
+ cc->remote = 1;
+ }
+}
+
+void
+cc_usb_close_remote(struct cc_usb *cc)
+{
+ if (cc->remote) {
+ cc_usb_printf(cc, "~");
+ cc->remote = 0;
+ }
+}
+
static struct termios save_termios;
struct cc_usb *
@@ -344,16 +420,19 @@ cc_usb_open(char *tty)
save_termios = termios;
cfmakeraw(&termios);
tcsetattr(cc->fd, TCSAFLUSH, &termios);
- cc_usb_printf(cc, "E 0\nm 0\n");
- cc_usb_sync(cc);
- sleep(1);
- cc_usb_sync(cc);
+ cc_usb_printf(cc, "\nE 0\nm 0\n");
+ do {
+ cc->in_count = cc->in_pos = 0;
+ _cc_usb_sync(cc, 100);
+ } while (cc->in_count > 0);
return cc;
}
void
cc_usb_close(struct cc_usb *cc)
{
+ cc_usb_close_remote(cc);
+ cc_usb_sync(cc);
tcsetattr(cc->fd, TCSAFLUSH, &save_termios);
close (cc->fd);
free (cc);
diff --git a/ao-tools/lib/cc-usb.h b/ao-tools/lib/cc-usb.h
index d7acfbd2..d3539281 100644
--- a/ao-tools/lib/cc-usb.h
+++ b/ao-tools/lib/cc-usb.h
@@ -53,7 +53,19 @@ cc_usb_sync(struct cc_usb *cc);
void
cc_queue_read(struct cc_usb *cc, uint8_t *buf, int len);
+int
+cc_usb_getchar(struct cc_usb *cc);
+
+void
+cc_usb_getline(struct cc_usb *cc, char *line, int max);
+
void
cc_usb_printf(struct cc_usb *cc, char *format, ...);
+void
+cc_usb_open_remote(struct cc_usb *cc, int channel);
+
+void
+cc_usb_close_remote(struct cc_usb *cc);
+
#endif /* _CC_USB_H_ */
diff --git a/ao-tools/lib/cc-usbdev.c b/ao-tools/lib/cc-usbdev.c
new file mode 100644
index 00000000..a19e231c
--- /dev/null
+++ b/ao-tools/lib/cc-usbdev.c
@@ -0,0 +1,317 @@
+/*
+ * 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 "cc.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char *
+load_string(char *dir, char *file)
+{
+ char *full = cc_fullname(dir, file);
+ char line[4096];
+ char *r;
+ FILE *f;
+ int rlen;
+
+ f = fopen(full, "r");
+ free(full);
+ if (!f)
+ return NULL;
+ r = fgets(line, sizeof (line), f);
+ fclose(f);
+ if (!r)
+ return NULL;
+ rlen = strlen(r);
+ if (r[rlen-1] == '\n')
+ r[rlen-1] = '\0';
+ return strdup(r);
+}
+
+static int
+load_hex(char *dir, char *file)
+{
+ char *line;
+ char *end;
+ long i;
+
+ line = load_string(dir, file);
+ if (!line)
+ return -1;
+ i = strtol(line, &end, 16);
+ free(line);
+ if (end == line)
+ return -1;
+ return i;
+}
+
+static int
+load_dec(char *dir, char *file)
+{
+ char *line;
+ char *end;
+ long i;
+
+ line = load_string(dir, file);
+ if (!line)
+ return -1;
+ i = strtol(line, &end, 10);
+ free(line);
+ if (end == line)
+ return -1;
+ return i;
+}
+
+static int
+dir_filter_tty_colon(const struct dirent *d)
+{
+ return strncmp(d->d_name, "tty:", 4) == 0;
+}
+
+static int
+dir_filter_tty(const struct dirent *d)
+{
+ return strncmp(d->d_name, "tty", 3) == 0;
+}
+
+static char *
+usb_tty(char *sys)
+{
+ char *base;
+ int num_configs;
+ int config;
+ struct dirent **namelist;
+ int interface;
+ int num_interfaces;
+ char endpoint_base[20];
+ char *endpoint_full;
+ char *tty_dir;
+ int ntty;
+ char *tty;
+
+ base = cc_basename(sys);
+ num_configs = load_hex(sys, "bNumConfigurations");
+ num_interfaces = load_hex(sys, "bNumInterfaces");
+ for (config = 1; config <= num_configs; config++) {
+ for (interface = 0; interface < num_interfaces; interface++) {
+ sprintf(endpoint_base, "%s:%d.%d",
+ base, config, interface);
+ endpoint_full = cc_fullname(sys, endpoint_base);
+
+ /* Check for tty:ttyACMx style names
+ */
+ ntty = scandir(endpoint_full, &namelist,
+ dir_filter_tty_colon,
+ alphasort);
+ if (ntty > 0) {
+ free(endpoint_full);
+ tty = cc_fullname("/dev", namelist[0]->d_name + 4);
+ free(namelist);
+ return tty;
+ }
+
+ /* Check for tty/ttyACMx style names
+ */
+ tty_dir = cc_fullname(endpoint_full, "tty");
+ free(endpoint_full);
+ ntty = scandir(tty_dir, &namelist,
+ dir_filter_tty,
+ alphasort);
+ free (tty_dir);
+ if (ntty > 0) {
+ tty = cc_fullname("/dev", namelist[0]->d_name);
+ free(namelist);
+ return tty;
+ }
+ }
+ }
+ return NULL;
+}
+
+static struct cc_usbdev *
+usb_scan_device(char *sys)
+{
+ struct cc_usbdev *usbdev;
+
+ usbdev = calloc(1, sizeof (struct cc_usbdev));
+ if (!usbdev)
+ return NULL;
+ usbdev->sys = strdup(sys);
+ usbdev->manufacturer = load_string(sys, "manufacturer");
+ usbdev->product = load_string(sys, "product");
+ usbdev->serial = load_dec(sys, "serial");
+ usbdev->idProduct = load_hex(sys, "idProduct");
+ usbdev->idVendor = load_hex(sys, "idVendor");
+ usbdev->tty = usb_tty(sys);
+ return usbdev;
+}
+
+static void
+usbdev_free(struct cc_usbdev *usbdev)
+{
+ free(usbdev->sys);
+ free(usbdev->manufacturer);
+ free(usbdev->product);
+ /* this can get used as a return value */
+ if (usbdev->tty)
+ free(usbdev->tty);
+ free(usbdev);
+}
+
+#define USB_DEVICES "/sys/bus/usb/devices"
+
+static int
+dir_filter_dev(const struct dirent *d)
+{
+ const char *n = d->d_name;
+ char c;
+
+ while ((c = *n++)) {
+ if (isdigit(c))
+ continue;
+ if (c == '-')
+ continue;
+ if (c == '.' && n != d->d_name + 1)
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+struct cc_usbdevs *
+cc_usbdevs_scan(void)
+{
+ int e;
+ struct dirent **ents;
+ char *dir;
+ struct cc_usbdev *dev;
+ struct cc_usbdevs *devs;
+ int n;
+
+ devs = calloc(1, sizeof (struct cc_usbdevs));
+ if (!devs)
+ return NULL;
+
+ n = scandir (USB_DEVICES, &ents,
+ dir_filter_dev,
+ alphasort);
+ if (!n)
+ return 0;
+ for (e = 0; e < n; e++) {
+ dir = cc_fullname(USB_DEVICES, ents[e]->d_name);
+ dev = usb_scan_device(dir);
+ free(dir);
+ if (dev->idVendor == 0xfffe && dev->tty) {
+ if (devs->dev)
+ devs->dev = realloc(devs->dev,
+ (devs->ndev + 1) * sizeof (struct usbdev *));
+ else
+ devs->dev = malloc (sizeof (struct usbdev *));
+ devs->dev[devs->ndev++] = dev;
+ }
+ }
+ free(ents);
+ return devs;
+}
+
+void
+cc_usbdevs_free(struct cc_usbdevs *usbdevs)
+{
+ int i;
+
+ if (!usbdevs)
+ return;
+ for (i = 0; i < usbdevs->ndev; i++)
+ usbdev_free(usbdevs->dev[i]);
+ free(usbdevs);
+}
+
+static char *
+match_dev(char *product, int serial)
+{
+ struct cc_usbdevs *devs;
+ struct cc_usbdev *dev;
+ int i;
+ char *tty = NULL;
+
+ devs = cc_usbdevs_scan();
+ if (!devs)
+ return NULL;
+ for (i = 0; i < devs->ndev; i++) {
+ dev = devs->dev[i];
+ if (product && strncmp (product, dev->product, strlen(product)) != 0)
+ continue;
+ if (serial && serial != dev->serial)
+ continue;
+ break;
+ }
+ if (i < devs->ndev) {
+ tty = devs->dev[i]->tty;
+ devs->dev[i]->tty = NULL;
+ }
+ cc_usbdevs_free(devs);
+ return tty;
+}
+
+char *
+cc_usbdevs_find_by_arg(char *arg, char *default_product)
+{
+ char *product;
+ int serial;
+ char *end;
+ char *colon;
+ char *tty;
+
+ if (arg)
+ {
+ /* check for <serial> */
+ serial = strtol(arg, &end, 0);
+ if (end != arg) {
+ if (*end != '\0')
+ return NULL;
+ product = NULL;
+ } else {
+ /* check for <product>:<serial> */
+ colon = strchr(arg, ':');
+ if (colon) {
+ product = strndup(arg, colon - arg);
+ serial = strtol(colon + 1, &end, 0);
+ if (*end != '\0')
+ return NULL;
+ } else {
+ product = arg;
+ serial = 0;
+ }
+ }
+ } else {
+ product = NULL;
+ serial = 0;
+ }
+ tty = NULL;
+ if (!product && default_product)
+ tty = match_dev(default_product, serial);
+ if (!tty)
+ tty = match_dev(product, serial);
+ if (product && product != arg)
+ free(product);
+ return tty;
+}
diff --git a/ao-tools/lib/cc-util.c b/ao-tools/lib/cc-util.c
new file mode 100644
index 00000000..65488ee9
--- /dev/null
+++ b/ao-tools/lib/cc-util.c
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include "cc.h"
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+
+char *
+cc_fullname (char *dir, char *file)
+{
+ char *new;
+ int dlen = strlen (dir);
+ int flen = strlen (file);
+ int slen = 0;
+
+ if (dir[dlen-1] != '/')
+ slen = 1;
+ new = malloc (dlen + slen + flen + 1);
+ if (!new)
+ return 0;
+ strcpy(new, dir);
+ if (slen)
+ strcat (new, "/");
+ strcat(new, file);
+ return new;
+}
+
+char *
+cc_basename(char *file)
+{
+ char *b;
+
+ b = strrchr(file, '/');
+ if (!b)
+ return file;
+ return b + 1;
+}
+
+int
+cc_mkdir(char *dir)
+{
+ char *slash;
+ char *d;
+ char *part;
+
+ d = dir;
+ for (;;) {
+ slash = strchr (d, '/');
+ if (!slash)
+ slash = d + strlen(d);
+ if (!*slash)
+ break;
+ part = strndup(dir, slash - dir);
+ if (!access(part, F_OK))
+ if (mkdir(part, 0777) < 0)
+ return -errno;
+ free(part);
+ d = slash + 1;
+ }
+ return 0;
+}
diff --git a/ao-tools/lib/cc.h b/ao-tools/lib/cc.h
new file mode 100644
index 00000000..6257ee44
--- /dev/null
+++ b/ao-tools/lib/cc.h
@@ -0,0 +1,373 @@
+/*
+ * 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.
+ */
+
+#ifndef _CC_H_
+#define _CC_H_
+
+#include <stdio.h>
+#include <stdint.h>
+#include "cc-telemetry.h"
+
+char *
+cc_fullname (char *dir, char *file);
+
+char *
+cc_basename(char *file);
+
+int
+cc_mkdir(char *dir);
+
+struct cc_usbdev {
+ char *sys;
+ char *tty;
+ char *manufacturer;
+ char *product;
+ int serial; /* AltOS always uses simple integer serial numbers */
+ int idProduct;
+ int idVendor;
+};
+
+struct cc_usbdevs {
+ struct cc_usbdev **dev;
+ int ndev;
+};
+
+void
+cc_usbdevs_free(struct cc_usbdevs *usbdevs);
+
+struct cc_usbdevs *
+cc_usbdevs_scan(void);
+
+char *
+cc_usbdevs_find_by_arg(char *arg, char *default_product);
+
+void
+cc_set_log_dir(char *dir);
+
+char *
+cc_get_log_dir(void);
+
+char *
+cc_make_filename(int serial, int flight, 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;
+ double time_offset;
+};
+
+
+/*
+ * For GPS data
+ */
+
+struct cc_gpselt {
+ double time;
+ int hour;
+ int minute;
+ int second;
+ int flags;
+ double lat;
+ double lon;
+ double alt;
+};
+
+#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_gpssat {
+ double time;
+ uint16_t svid;
+ uint8_t c_n;
+};
+
+struct cc_gpssats {
+ int nsat;
+ struct cc_gpssat sat[12];
+};
+
+struct cc_gpsdata {
+ int num;
+ int size;
+ struct cc_gpselt *data;
+ double time_offset;
+ int numsats;
+ int sizesats;
+ struct cc_gpssats *sats;
+};
+
+/*
+ * 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;
+ int year, month, day;
+ 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 {
+ double flight_start;
+ double flight_stop;
+
+ 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;
+
+ /* unfiltered, but converted */
+ struct cc_timedata pres;
+ struct cc_timedata accel;
+ struct cc_timedata state;
+};
+
+/*
+ * Telemetry data contents
+ */
+
+
+struct cc_gps_time {
+ int year;
+ int month;
+ int day;
+ 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 c_n0;
+};
+
+struct cc_gps_tracking {
+ int channels;
+ struct cc_gps_sat sats[12];
+};
+
+struct cc_telem {
+ char callsign[16];
+ int serial;
+ int flight;
+ 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;
+ int accel_plus_g;
+ int accel_minus_g;
+ 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);
+
+void
+cc_timedata_limits(struct cc_timedata *d, double min_time, double max_time, int *start, int *stop);
+
+int
+cc_timedata_min(struct cc_timedata *d, double min_time, double max_time);
+
+int
+cc_timedata_min_mag(struct cc_timedata *d, double min_time, double max_time);
+
+int
+cc_timedata_max(struct cc_timedata *d, double min_time, double max_time);
+
+int
+cc_timedata_max_mag(struct cc_timedata *d, double min_time, double max_time);
+
+double
+cc_timedata_average(struct cc_timedata *d, double min_time, double max_time);
+
+double
+cc_timedata_average_mag(struct cc_timedata *d, double min_time, double max_time);
+
+int
+cc_perioddata_limits(struct cc_perioddata *d, double min_time, double max_time, int *start, int *stop);
+
+int
+cc_perioddata_min(struct cc_perioddata *d, double min_time, double max_time);
+
+int
+cc_perioddata_min_mag(struct cc_perioddata *d, double min_time, double max_time);
+
+int
+cc_perioddata_max(struct cc_perioddata *d, double min_time, double max_time);
+
+int
+cc_perioddata_max_mag(struct cc_perioddata *d, double min_time, double max_time);
+
+double
+cc_perioddata_average(struct cc_perioddata *d, double min_time, double max_time);
+
+double
+cc_perioddata_average_mag(struct cc_perioddata *d, double min_time, double max_time);
+
+double *
+cc_low_pass(double *data, int data_len, double omega_pass, double omega_stop, double error);
+
+struct cc_perioddata *
+cc_period_make(struct cc_timedata *td, double start_time, double stop_time);
+
+struct cc_perioddata *
+cc_period_low_pass(struct cc_perioddata *raw, double omega_pass, double omega_stop, double error);
+
+struct cc_timedata *
+cc_timedata_convert(struct cc_timedata *d, double (*f)(double v, double a), double a);
+
+struct cc_timedata *
+cc_timedata_integrate(struct cc_timedata *d, double min_time, double max_time);
+
+struct cc_perioddata *
+cc_perioddata_differentiate(struct cc_perioddata *i);
+
+struct cc_flightcooked *
+cc_flight_cook(struct cc_flightraw *raw);
+
+void
+cc_flightcooked_free(struct cc_flightcooked *cooked);
+
+#endif /* _CC_H_ */
diff --git a/ao-tools/lib/cephes.h b/ao-tools/lib/cephes.h
new file mode 100644
index 00000000..f8ec264f
--- /dev/null
+++ b/ao-tools/lib/cephes.h
@@ -0,0 +1,122 @@
+/*
+ * This file comes from the cephes math library, which was
+ * released under the GPLV2+ license as a part of the Debian labplot
+ * package (I've included the GPLV2 license reference here to make
+ * this clear) - Keith Packard <keithp@keithp.com>
+ *
+ * Cephes Math Library Release 2.0: April, 1987
+ * Copyright 1984, 1987 by Stephen L. Moshier
+ * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+ *
+ * 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.
+ */
+/*
+ * Prototypes of Cephes functions
+ */
+
+#ifndef _CEPHES_H_
+#define _CEPHES_H_
+
+/* Variable for error reporting. See mtherr.c. */
+extern int merror;
+
+#if 0
+extern int airy ( double x, double *ai, double *aip, double *bi, double *bip );
+extern double beta ( double a, double b );
+extern double lbeta ( double a, double b );
+extern double chdtrc ( double df, double x );
+extern double chdtr ( double df, double x );
+extern double chdtri ( double df, double y );
+extern double dawsn ( double xx );
+extern double ellie ( double phi, double m );
+extern double ellik ( double phi, double m );
+extern double ellpe ( double x );
+extern double ellpk ( double x );
+extern double expn ( int n, double x );
+extern double fac ( int i );
+extern double fdtrc ( int ia, int ib, double x );
+extern double fdtr ( int ia, int ib, double x );
+extern double fdtri ( int ia, int ib, double y );
+extern double frexp ( double x, int *pw2 );
+extern double ldexp ( double x, int pw2 );
+extern int fresnl ( double xxa, double *ssa, double *cca );
+extern double gdtr ( double a, double b, double x );
+extern double gdtrc ( double a, double b, double x );
+extern double hyp2f0 ( double a, double b, double x, int type, double *err );
+extern double hyp2f1 ( double a, double b, double c, double x );
+extern double hyperg ( double a, double b, double x );
+#endif
+extern double i0 ( double x );
+extern double i0e ( double x );
+#if 0
+extern double i1 ( double x );
+extern double i1e ( double x );
+extern double iv ( double v, double x );
+extern double igamc ( double a, double x );
+extern double igam ( double a, double x );
+extern double igami ( double a, double y0_ );
+extern double incbet ( double aa, double bb, double xx );
+extern double incbi ( double aa, double bb, double yy0 );
+extern double jv ( double n, double x );
+extern double k0 ( double x );
+extern double k0e ( double x );
+extern double k1 ( double x );
+extern double k1e ( double x );
+extern double kn ( int nn, double x );
+extern int mtherr ( char *name, int code );
+extern double ndtr ( double a );
+extern double ndtri ( double y0_ );
+extern double pdtrc ( int k, double m );
+extern double pdtr ( int k, double m );
+extern double pdtri ( int k, double y );
+extern double psi ( double x );
+extern void revers ( double y[], double x[], int n );
+extern double true_gamma ( double x );
+extern double rgamma ( double x );
+extern int shichi ( double x, double *si, double *ci );
+extern int sici ( double x, double *si, double *ci );
+extern double spence ( double x );
+extern double stdtr ( int k, double t );
+extern double stdtri ( int k, double p );
+extern double onef2 ( double a, double b, double c, double x, double *err );
+extern double threef0 ( double a, double b, double c, double x, double *err );
+extern double struve ( double v, double x );
+extern double log1p ( double x );
+extern double expm1 ( double x );
+extern double cosm1 ( double x );
+extern double yv ( double v, double x );
+extern double zeta ( double x, double q );
+extern double zetac ( double x );
+
+#endif
+extern double chbevl ( double x, void *P, int n );
+#if 0
+extern double polevl ( double x, void *P, int n );
+extern double p1evl ( double x, void *P, int n );
+
+/* polyn.c */
+extern void polini ( int maxdeg );
+extern void polprt ( double a[], int na, int d );
+extern void polclr ( double *a, int n );
+extern void polmov ( double *a, int na, double *b );
+extern void polmul ( double a[], int na, double b[], int nb, double c[] );
+extern void poladd ( double a[], int na, double b[], int nb, double c[] );
+extern void polsub ( double a[], int na, double b[], int nb, double c[] );
+extern int poldiv ( double a[], int na, double b[], int nb, double c[] );
+extern void polsbt ( double a[], int na, double b[], int nb, double c[] );
+extern double poleva ( double a[], int na, double x );
+
+#endif
+
+#endif /* _CEPHES_H_ */
diff --git a/ao-tools/lib/chbevl.c b/ao-tools/lib/chbevl.c
new file mode 100644
index 00000000..22892413
--- /dev/null
+++ b/ao-tools/lib/chbevl.c
@@ -0,0 +1,81 @@
+/* chbevl.c
+ *
+ * Evaluate Chebyshev series
+ *
+ *
+ *
+ * SYNOPSIS:
+ *
+ * int N;
+ * double x, y, coef[N], chebevl();
+ *
+ * y = chbevl( x, coef, N );
+ *
+ *
+ *
+ * DESCRIPTION:
+ *
+ * Evaluates the series
+ *
+ * N-1
+ * - '
+ * y = > coef[i] T (x/2)
+ * - i
+ * i=0
+ *
+ * of Chebyshev polynomials Ti at argument x/2.
+ *
+ * Coefficients are stored in reverse order, i.e. the zero
+ * order term is last in the array. Note N is the number of
+ * coefficients, not the order.
+ *
+ * If coefficients are for the interval a to b, x must
+ * have been transformed to x -> 2(2x - b - a)/(b-a) before
+ * entering the routine. This maps x from (a, b) to (-1, 1),
+ * over which the Chebyshev polynomials are defined.
+ *
+ * If the coefficients are for the inverted interval, in
+ * which (a, b) is mapped to (1/b, 1/a), the transformation
+ * required is x -> 2(2ab/x - b - a)/(b-a). If b is infinity,
+ * this becomes x -> 4a/x - 1.
+ *
+ *
+ *
+ * SPEED:
+ *
+ * Taking advantage of the recurrence properties of the
+ * Chebyshev polynomials, the routine requires one more
+ * addition per loop than evaluating a nested polynomial of
+ * the same degree.
+ *
+ */
+ /* chbevl.c */
+
+/*
+Cephes Math Library Release 2.0: April, 1987
+Copyright 1985, 1987 by Stephen L. Moshier
+Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+*/
+
+#include "cephes.h"
+
+double chbevl(double x,void* array,int n )
+{
+double b0, b1, b2, *p;
+int i;
+
+p = (double *) array;
+b0 = *p++;
+b1 = 0.0;
+i = n - 1;
+
+do
+ {
+ b2 = b1;
+ b1 = b0;
+ b0 = x * b1 - b2 + *p++;
+ }
+while( --i );
+
+return( 0.5*(b0-b2) );
+}
diff --git a/ao-tools/lib/cmath.h b/ao-tools/lib/cmath.h
new file mode 100644
index 00000000..2751aecf
--- /dev/null
+++ b/ao-tools/lib/cmath.h
@@ -0,0 +1,179 @@
+/*
+ * Grace - GRaphing, Advanced Computation and Exploration of data
+ *
+ * Home page: http://plasma-gate.weizmann.ac.il/Grace/
+ *
+ * Copyright (c) 1991-1995 Paul J Turner, Portland, OR
+ * Copyright (c) 1996-2000 Grace Development Team
+ *
+ * Maintained by Evgeny Stambulchik <fnevgeny@plasma-gate.weizmann.ac.il>
+ *
+ *
+ * All Rights Reserved
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* cmath.h - replacement for math.h or missing in libm functions */
+
+#if defined(HAVE_MATH_H)
+# include <math.h>
+#endif
+#if defined(HAVE_FLOAT_H)
+# include <float.h>
+#endif
+#if defined(HAVE_IEEEFP_H)
+# include <ieeefp.h>
+#endif
+
+#ifndef __GRACE_SOURCE_
+
+#ifndef MACHEP
+extern double MACHEP;
+#endif
+
+#ifndef UFLOWTHRESH
+extern double UFLOWTHRESH;
+#endif
+
+#ifndef MAXNUM
+extern double MAXNUM;
+#endif
+
+#endif /* __GRACE_SOURCE_ */
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+#ifndef M_SQRT2
+# define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+
+#ifndef M_SQRT1_2
+# define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+
+#ifndef M_SQRT1_3
+# define M_SQRT1_3 0.57735026918962576451 /* 1/sqrt(3) */
+#endif
+
+#ifndef HAVE_HYPOT
+# define hypot(x, y) sqrt((x)*(x) + (y)*(y))
+#endif
+
+extern double round ( double x );
+#ifndef HAVE_RINT
+# define rint round
+#else
+# ifndef HAVE_RINT_DECL
+extern double rint ( double x );
+# endif
+#endif
+
+#ifndef HAVE_CBRT_DECL
+extern double cbrt ( double x );
+#endif
+
+/* Cygnus gnuwin32 has the log2 macro */
+#ifdef log2
+# undef log2
+#endif
+
+#ifndef HAVE_LOG2_DECL
+extern double log2 ( double x );
+#endif
+
+#ifndef HAVE_LGAMMA
+extern int sgngam;
+# define lgamma lgam
+# define signgam sgngam
+extern double lgam ( double x );
+#else
+# ifndef HAVE_LGAMMA_DECL
+extern double lgamma ( double x );
+# endif
+# ifndef HAVE_SIGNGAM_DECL
+extern int signgam;
+# endif
+# define lgam lgamma
+# define sgngam signgam
+#endif
+
+#ifndef HAVE_ACOSH_DECL
+extern double acosh ( double x );
+#endif
+
+#ifndef HAVE_ASINH_DECL
+extern double asinh ( double x );
+#endif
+
+#ifndef HAVE_ATANH_DECL
+extern double atanh ( double x );
+#endif
+
+#ifndef HAVE_ERF_DECL
+extern double erf ( double x );
+#endif
+
+#ifndef HAVE_ERFC_DECL
+extern double erfc ( double x );
+#endif
+
+#ifndef HAVE_Y0_DECL
+extern double y0 ( double x );
+#endif
+#ifndef HAVE_Y1_DECL
+extern double y1 ( double x );
+#endif
+#ifndef HAVE_YN_DECL
+extern double yn ( int n, double x );
+#endif
+#ifndef HAVE_J0_DECL
+extern double j0 ( double x );
+#endif
+#ifndef HAVE_J1_DECL
+extern double j1 ( double x );
+#endif
+#ifndef HAVE_JN_DECL
+extern double jn ( int n, double x );
+#endif
+
+/* isfinite is a macro */
+#ifdef isfinite
+# define HAVE_ISFINITE_MACRO
+#endif
+
+#ifndef HAVE_FINITE
+# define finite isfinite
+# if !defined(HAVE_ISFINITE_DECL) && !defined(HAVE_ISFINITE_MACRO)
+extern int isfinite ( double x );
+# endif
+#else
+# ifndef HAVE_FINITE_DECL
+extern int finite ( double x );
+# endif
+#endif
+
+#ifndef HAVE_ISNAN_DECL
+#ifdef __FreeBSD__
+# include <sys/param.h>
+# if __FreeBSD_version < 500100
+extern int isnan ( double x );
+# endif
+#endif
+#else
+extern int isnan ( double x );
+#endif
diff --git a/ao-tools/lib/i0.c b/ao-tools/lib/i0.c
new file mode 100644
index 00000000..6f7b5a57
--- /dev/null
+++ b/ao-tools/lib/i0.c
@@ -0,0 +1,414 @@
+/*
+ * This file comes from the cephes math library, which was
+ * released under the GPLV2+ license as a part of the Debian labplot
+ * package (I've included the GPLV2 license reference here to make
+ * this clear) - Keith Packard <keithp@keithp.com>
+ *
+ * Cephes Math Library Release 2.0: April, 1987
+ * Copyright 1984, 1987 by Stephen L. Moshier
+ * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+ *
+ * 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.
+ */
+
+/* i0.c
+ *
+ * Modified Bessel function of order zero
+ *
+ *
+ *
+ * SYNOPSIS:
+ *
+ * double x, y, i0();
+ *
+ * y = i0( x );
+ *
+ *
+ *
+ * DESCRIPTION:
+ *
+ * Returns modified Bessel function of order zero of the
+ * argument.
+ *
+ * The function is defined as i0(x) = j0( ix ).
+ *
+ * The range is partitioned into the two intervals [0,8] and
+ * (8, infinity). Chebyshev polynomial expansions are employed
+ * in each interval.
+ *
+ *
+ *
+ * ACCURACY:
+ *
+ * Relative error:
+ * arithmetic domain # trials peak rms
+ * DEC 0,30 6000 8.2e-17 1.9e-17
+ * IEEE 0,30 30000 5.8e-16 1.4e-16
+ *
+ */
+ /* i0e.c
+ *
+ * Modified Bessel function of order zero,
+ * exponentially scaled
+ *
+ *
+ *
+ * SYNOPSIS:
+ *
+ * double x, y, i0e();
+ *
+ * y = i0e( x );
+ *
+ *
+ *
+ * DESCRIPTION:
+ *
+ * Returns exponentially scaled modified Bessel function
+ * of order zero of the argument.
+ *
+ * The function is defined as i0e(x) = exp(-|x|) j0( ix ).
+ *
+ *
+ *
+ * ACCURACY:
+ *
+ * Relative error:
+ * arithmetic domain # trials peak rms
+ * IEEE 0,30 30000 5.4e-16 1.2e-16
+ * See i0().
+ *
+ */
+
+/* i0.c */
+
+
+/*
+Cephes Math Library Release 2.0: April, 1987
+Copyright 1984, 1987 by Stephen L. Moshier
+Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+*/
+
+#include <math.h>
+#include "mconf.h"
+#include "cephes.h"
+
+/* Chebyshev coefficients for exp(-x) I0(x)
+ * in the interval [0,8].
+ *
+ * lim(x->0){ exp(-x) I0(x) } = 1.
+ */
+
+#ifdef UNK
+static double A[] =
+{
+-4.41534164647933937950E-18,
+ 3.33079451882223809783E-17,
+-2.43127984654795469359E-16,
+ 1.71539128555513303061E-15,
+-1.16853328779934516808E-14,
+ 7.67618549860493561688E-14,
+-4.85644678311192946090E-13,
+ 2.95505266312963983461E-12,
+-1.72682629144155570723E-11,
+ 9.67580903537323691224E-11,
+-5.18979560163526290666E-10,
+ 2.65982372468238665035E-9,
+-1.30002500998624804212E-8,
+ 6.04699502254191894932E-8,
+-2.67079385394061173391E-7,
+ 1.11738753912010371815E-6,
+-4.41673835845875056359E-6,
+ 1.64484480707288970893E-5,
+-5.75419501008210370398E-5,
+ 1.88502885095841655729E-4,
+-5.76375574538582365885E-4,
+ 1.63947561694133579842E-3,
+-4.32430999505057594430E-3,
+ 1.05464603945949983183E-2,
+-2.37374148058994688156E-2,
+ 4.93052842396707084878E-2,
+-9.49010970480476444210E-2,
+ 1.71620901522208775349E-1,
+-3.04682672343198398683E-1,
+ 6.76795274409476084995E-1
+};
+#endif
+
+#ifdef DEC
+static unsigned short A[] = {
+0121642,0162671,0004646,0103567,
+0022431,0115424,0135755,0026104,
+0123214,0023533,0110365,0156635,
+0023767,0033304,0117662,0172716,
+0124522,0100426,0012277,0157531,
+0025254,0155062,0054461,0030465,
+0126010,0131143,0013560,0153604,
+0026517,0170577,0006336,0114437,
+0127227,0162253,0152243,0052734,
+0027724,0142766,0061641,0160200,
+0130416,0123760,0116564,0125262,
+0031066,0144035,0021246,0054641,
+0131537,0053664,0060131,0102530,
+0032201,0155664,0165153,0020652,
+0132617,0061434,0074423,0176145,
+0033225,0174444,0136147,0122542,
+0133624,0031576,0056453,0020470,
+0034211,0175305,0172321,0041314,
+0134561,0054462,0147040,0165315,
+0035105,0124333,0120203,0162532,
+0135427,0013750,0174257,0055221,
+0035726,0161654,0050220,0100162,
+0136215,0131361,0000325,0041110,
+0036454,0145417,0117357,0017352,
+0136702,0072367,0104415,0133574,
+0037111,0172126,0072505,0014544,
+0137302,0055601,0120550,0033523,
+0037457,0136543,0136544,0043002,
+0137633,0177536,0001276,0066150,
+0040055,0041164,0100655,0010521
+};
+#endif
+
+#ifdef IBMPC
+static unsigned short A[] = {
+0xd0ef,0x2134,0x5cb7,0xbc54,
+0xa589,0x977d,0x3362,0x3c83,
+0xbbb4,0x721e,0x84eb,0xbcb1,
+0x5eba,0x93f6,0xe6d8,0x3cde,
+0xfbeb,0xc297,0x5022,0xbd0a,
+0x2627,0x4b26,0x9b46,0x3d35,
+0x1af0,0x62ee,0x164c,0xbd61,
+0xd324,0xe19b,0xfe2f,0x3d89,
+0x6abc,0x7a94,0xfc95,0xbdb2,
+0x3c10,0xcc74,0x98be,0x3dda,
+0x9556,0x13ae,0xd4fe,0xbe01,
+0xcb34,0xa454,0xd903,0x3e26,
+0x30ab,0x8c0b,0xeaf6,0xbe4b,
+0x6435,0x9d4d,0x3b76,0x3e70,
+0x7f8d,0x8f22,0xec63,0xbe91,
+0xf4ac,0x978c,0xbf24,0x3eb2,
+0x6427,0xcba5,0x866f,0xbed2,
+0x2859,0xbe9a,0x3f58,0x3ef1,
+0x1d5a,0x59c4,0x2b26,0xbf0e,
+0x7cab,0x7410,0xb51b,0x3f28,
+0xeb52,0x1f15,0xe2fd,0xbf42,
+0x100e,0x8a12,0xdc75,0x3f5a,
+0xa849,0x201a,0xb65e,0xbf71,
+0xe3dd,0xf3dd,0x9961,0x3f85,
+0xb6f0,0xf121,0x4e9e,0xbf98,
+0xa32d,0xcea8,0x3e8a,0x3fa9,
+0x06ea,0x342d,0x4b70,0xbfb8,
+0x88c0,0x77ac,0xf7ac,0x3fc5,
+0xcd8d,0xc057,0x7feb,0xbfd3,
+0xa22a,0x9035,0xa84e,0x3fe5,
+};
+#endif
+
+#ifdef MIEEE
+static unsigned short A[] = {
+0xbc54,0x5cb7,0x2134,0xd0ef,
+0x3c83,0x3362,0x977d,0xa589,
+0xbcb1,0x84eb,0x721e,0xbbb4,
+0x3cde,0xe6d8,0x93f6,0x5eba,
+0xbd0a,0x5022,0xc297,0xfbeb,
+0x3d35,0x9b46,0x4b26,0x2627,
+0xbd61,0x164c,0x62ee,0x1af0,
+0x3d89,0xfe2f,0xe19b,0xd324,
+0xbdb2,0xfc95,0x7a94,0x6abc,
+0x3dda,0x98be,0xcc74,0x3c10,
+0xbe01,0xd4fe,0x13ae,0x9556,
+0x3e26,0xd903,0xa454,0xcb34,
+0xbe4b,0xeaf6,0x8c0b,0x30ab,
+0x3e70,0x3b76,0x9d4d,0x6435,
+0xbe91,0xec63,0x8f22,0x7f8d,
+0x3eb2,0xbf24,0x978c,0xf4ac,
+0xbed2,0x866f,0xcba5,0x6427,
+0x3ef1,0x3f58,0xbe9a,0x2859,
+0xbf0e,0x2b26,0x59c4,0x1d5a,
+0x3f28,0xb51b,0x7410,0x7cab,
+0xbf42,0xe2fd,0x1f15,0xeb52,
+0x3f5a,0xdc75,0x8a12,0x100e,
+0xbf71,0xb65e,0x201a,0xa849,
+0x3f85,0x9961,0xf3dd,0xe3dd,
+0xbf98,0x4e9e,0xf121,0xb6f0,
+0x3fa9,0x3e8a,0xcea8,0xa32d,
+0xbfb8,0x4b70,0x342d,0x06ea,
+0x3fc5,0xf7ac,0x77ac,0x88c0,
+0xbfd3,0x7feb,0xc057,0xcd8d,
+0x3fe5,0xa84e,0x9035,0xa22a
+};
+#endif
+
+
+/* Chebyshev coefficients for exp(-x) sqrt(x) I0(x)
+ * in the inverted interval [8,infinity].
+ *
+ * lim(x->inf){ exp(-x) sqrt(x) I0(x) } = 1/sqrt(2pi).
+ */
+
+#ifdef UNK
+static double B[] =
+{
+-7.23318048787475395456E-18,
+-4.83050448594418207126E-18,
+ 4.46562142029675999901E-17,
+ 3.46122286769746109310E-17,
+-2.82762398051658348494E-16,
+-3.42548561967721913462E-16,
+ 1.77256013305652638360E-15,
+ 3.81168066935262242075E-15,
+-9.55484669882830764870E-15,
+-4.15056934728722208663E-14,
+ 1.54008621752140982691E-14,
+ 3.85277838274214270114E-13,
+ 7.18012445138366623367E-13,
+-1.79417853150680611778E-12,
+-1.32158118404477131188E-11,
+-3.14991652796324136454E-11,
+ 1.18891471078464383424E-11,
+ 4.94060238822496958910E-10,
+ 3.39623202570838634515E-9,
+ 2.26666899049817806459E-8,
+ 2.04891858946906374183E-7,
+ 2.89137052083475648297E-6,
+ 6.88975834691682398426E-5,
+ 3.36911647825569408990E-3,
+ 8.04490411014108831608E-1
+};
+#endif
+
+#ifdef DEC
+static unsigned short B[] = {
+0122005,0066672,0123124,0054311,
+0121662,0033323,0030214,0104602,
+0022515,0170300,0113314,0020413,
+0022437,0117350,0035402,0007146,
+0123243,0000135,0057220,0177435,
+0123305,0073476,0144106,0170702,
+0023777,0071755,0017527,0154373,
+0024211,0052214,0102247,0033270,
+0124454,0017763,0171453,0012322,
+0125072,0166316,0075505,0154616,
+0024612,0133770,0065376,0025045,
+0025730,0162143,0056036,0001632,
+0026112,0015077,0150464,0063542,
+0126374,0101030,0014274,0065457,
+0127150,0077271,0125763,0157617,
+0127412,0104350,0040713,0120445,
+0027121,0023765,0057500,0001165,
+0030407,0147146,0003643,0075644,
+0031151,0061445,0044422,0156065,
+0031702,0132224,0003266,0125551,
+0032534,0000076,0147153,0005555,
+0033502,0004536,0004016,0026055,
+0034620,0076433,0142314,0171215,
+0036134,0146145,0013454,0101104,
+0040115,0171425,0062500,0047133
+};
+#endif
+
+#ifdef IBMPC
+static unsigned short B[] = {
+0x8b19,0x54ca,0xadb7,0xbc60,
+0x9130,0x6611,0x46da,0xbc56,
+0x8421,0x12d9,0xbe18,0x3c89,
+0x41cd,0x0760,0xf3dd,0x3c83,
+0x1fe4,0xabd2,0x600b,0xbcb4,
+0xde38,0xd908,0xaee7,0xbcb8,
+0xfb1f,0xa3ea,0xee7d,0x3cdf,
+0xe6d7,0x9094,0x2a91,0x3cf1,
+0x629a,0x7e65,0x83fe,0xbd05,
+0xbb32,0xcf68,0x5d99,0xbd27,
+0xc545,0x0d5f,0x56ff,0x3d11,
+0xc073,0x6b83,0x1c8c,0x3d5b,
+0x8cec,0xfa26,0x4347,0x3d69,
+0x8d66,0x0317,0x9043,0xbd7f,
+0x7bf2,0x357e,0x0fd7,0xbdad,
+0x7425,0x0839,0x511d,0xbdc1,
+0x004f,0xabe8,0x24fe,0x3daa,
+0x6f75,0xc0f4,0xf9cc,0x3e00,
+0x5b87,0xa922,0x2c64,0x3e2d,
+0xd56d,0x80d6,0x5692,0x3e58,
+0x616e,0xd9cd,0x8007,0x3e8b,
+0xc586,0xc101,0x412b,0x3ec8,
+0x9e52,0x7899,0x0fa3,0x3f12,
+0x9049,0xa2e5,0x998c,0x3f6b,
+0x09cb,0xaca8,0xbe62,0x3fe9
+};
+#endif
+
+#ifdef MIEEE
+static unsigned short B[] = {
+0xbc60,0xadb7,0x54ca,0x8b19,
+0xbc56,0x46da,0x6611,0x9130,
+0x3c89,0xbe18,0x12d9,0x8421,
+0x3c83,0xf3dd,0x0760,0x41cd,
+0xbcb4,0x600b,0xabd2,0x1fe4,
+0xbcb8,0xaee7,0xd908,0xde38,
+0x3cdf,0xee7d,0xa3ea,0xfb1f,
+0x3cf1,0x2a91,0x9094,0xe6d7,
+0xbd05,0x83fe,0x7e65,0x629a,
+0xbd27,0x5d99,0xcf68,0xbb32,
+0x3d11,0x56ff,0x0d5f,0xc545,
+0x3d5b,0x1c8c,0x6b83,0xc073,
+0x3d69,0x4347,0xfa26,0x8cec,
+0xbd7f,0x9043,0x0317,0x8d66,
+0xbdad,0x0fd7,0x357e,0x7bf2,
+0xbdc1,0x511d,0x0839,0x7425,
+0x3daa,0x24fe,0xabe8,0x004f,
+0x3e00,0xf9cc,0xc0f4,0x6f75,
+0x3e2d,0x2c64,0xa922,0x5b87,
+0x3e58,0x5692,0x80d6,0xd56d,
+0x3e8b,0x8007,0xd9cd,0x616e,
+0x3ec8,0x412b,0xc101,0xc586,
+0x3f12,0x0fa3,0x7899,0x9e52,
+0x3f6b,0x998c,0xa2e5,0x9049,
+0x3fe9,0xbe62,0xaca8,0x09cb
+};
+#endif
+
+double i0(double x)
+{
+double y;
+
+if( x < 0 )
+ x = -x;
+if( x <= 8.0 )
+ {
+ y = (x/2.0) - 2.0;
+ return( exp(x) * chbevl( y, A, 30 ) );
+ }
+
+return( exp(x) * chbevl( 32.0/x - 2.0, B, 25 ) / sqrt(x) );
+
+}
+
+
+
+
+double i0e(double x )
+{
+double y;
+
+if( x < 0 )
+ x = -x;
+if( x <= 8.0 )
+ {
+ y = (x/2.0) - 2.0;
+ return( chbevl( y, A, 30 ) );
+ }
+
+return( chbevl( 32.0/x - 2.0, B, 25 ) / sqrt(x) );
+
+}
diff --git a/ao-tools/lib/mconf.h b/ao-tools/lib/mconf.h
new file mode 100644
index 00000000..af1ebb51
--- /dev/null
+++ b/ao-tools/lib/mconf.h
@@ -0,0 +1,211 @@
+/*
+ * This file comes from the cephes math library, which was
+ * released under the GPLV2+ license as a part of the Debian labplot
+ * package (I've included the GPLV2 license reference here to make
+ * this clear) - Keith Packard <keithp@keithp.com>
+ *
+ * Cephes Math Library Release 2.0: April, 1987
+ * Copyright 1984, 1987 by Stephen L. Moshier
+ * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
+ *
+ * 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.
+ */
+/* mconf.h
+ *
+ * Common include file for math routines
+ *
+ *
+ *
+ * SYNOPSIS:
+ *
+ * #include "mconf.h"
+ *
+ *
+ *
+ * DESCRIPTION:
+ *
+ * This file contains definitions for error codes that are
+ * passed to the common error handling routine mtherr()
+ * (which see).
+ *
+ * The file also includes a conditional assembly definition
+ * for the type of computer arithmetic (IEEE, DEC, Motorola
+ * IEEE, or UNKnown).
+ *
+ * For Digital Equipment PDP-11 and VAX computers, certain
+ * IBM systems, and others that use numbers with a 56-bit
+ * significand, the symbol DEC should be defined. In this
+ * mode, most floating point constants are given as arrays
+ * of octal integers to eliminate decimal to binary conversion
+ * errors that might be introduced by the compiler.
+ *
+ * For little-endian computers, such as IBM PC, that follow the
+ * IEEE Standard for Binary Floating Point Arithmetic (ANSI/IEEE
+ * Std 754-1985), the symbol IBMPC should be defined. These
+ * numbers have 53-bit significands. In this mode, constants
+ * are provided as arrays of hexadecimal 16 bit integers.
+ *
+ * Big-endian IEEE format is denoted MIEEE. On some RISC
+ * systems such as Sun SPARC, double precision constants
+ * must be stored on 8-byte address boundaries. Since integer
+ * arrays may be aligned differently, the MIEEE configuration
+ * may fail on such machines.
+ *
+ * To accommodate other types of computer arithmetic, all
+ * constants are also provided in a normal decimal radix
+ * which one can hope are correctly converted to a suitable
+ * format by the available C language compiler. To invoke
+ * this mode, define the symbol UNK.
+ *
+ * An important difference among these modes is a predefined
+ * set of machine arithmetic constants for each. The numbers
+ * MACHEP (the machine roundoff error), MAXNUM (largest number
+ * represented), and several other parameters are preset by
+ * the configuration symbol. Check the file const.c to
+ * ensure that these values are correct for your computer.
+ *
+ * Configurations NANS, INFINITIES, MINUSZERO, and DENORMAL
+ * may fail on many systems. Verify that they are supposed
+ * to work on your computer.
+ */
+
+/*
+Cephes Math Library Release 2.3: June, 1995
+Copyright 1984, 1987, 1989, 1995 by Stephen L. Moshier
+
+Adjusted for use with ACE/gr by Evgeny Stambulchik, October 1997
+*/
+
+#define __GRACE_SOURCE_
+
+#include "cmath.h"
+
+/* Type of computer arithmetic */
+/* In ACE/gr, defined as a compiler directive - no need to define here */
+
+/* PDP-11, Pro350, VAX:
+ */
+#if defined(HAVE_DEC_FPU)
+# define DEC 1
+#endif
+
+/* Intel IEEE, low order words come first:
+ */
+#if defined(HAVE_LIEEE_FPU)
+# define IBMPC 1
+#endif
+
+/* Motorola IEEE, high order words come first
+ * (Sun 680x0 workstation):
+ */
+#if defined(HAVE_BIEEE_FPU)
+# define MIEEE 1
+#endif
+
+/* UNKnown arithmetic, invokes coefficients given in
+ * normal decimal format. Beware of range boundary
+ * problems (MACHEP, MAXLOG, etc. in const.c) and
+ * roundoff problems in pow.c:
+ * (Sun SPARCstation)
+ */
+
+#if (!defined (DEC) && !defined (IBMPC) && !defined (MIEEE))
+# define UNK 1
+#endif
+
+/* Define this `volatile' if your compiler thinks
+ * that floating point arithmetic obeys the associative
+ * and distributive laws. It will defeat some optimizations
+ * (but probably not enough of them).
+ *
+ * #define VOLATILE volatile
+ */
+
+#ifndef VOLATILE
+# define VOLATILE
+#endif
+
+#ifdef PI
+# undef PI
+#endif
+
+#ifdef NAN
+# undef NAN
+#endif
+
+#ifdef INFINITY
+# undef INFINITY
+#endif
+
+/* Constant definitions for math error conditions
+ */
+
+#if defined(DOMAIN)
+# undef DOMAIN
+#endif
+#define DOMAIN 1 /* argument domain error */
+
+#if defined(SING)
+# undef SING
+#endif
+#define SING 2 /* argument singularity */
+
+#if defined(OVERFLOW)
+# undef OVERFLOW
+#endif
+#define OVERFLOW 3 /* overflow range error */
+
+#if defined(UNDERFLOW)
+# undef UNDERFLOW
+#endif
+#define UNDERFLOW 4 /* underflow range error */
+
+#if defined(TLOSS)
+# undef TLOSS
+#endif
+#define TLOSS 5 /* total loss of precision */
+
+#if defined(PLOSS)
+# undef PLOSS
+#endif
+#define PLOSS 6 /* partial loss of precision */
+
+#if defined(EDOM)
+# undef EDOM
+#endif
+#define EDOM 33
+
+#if defined(ERANGE)
+# undef ERANGE
+#endif
+#define ERANGE 34
+
+#if !defined (UNK)
+ /* Define to support tiny denormal numbers, else undefine. */
+# define DENORMAL 1
+
+ /* Define to ask for infinity support, else undefine. */
+# define INFINITIES 1
+
+ /* Define to ask for support of numbers that are Not-a-Number,
+ else undefine. This may automatically define INFINITIES in some files. */
+# define NANS 1
+
+ /* Define to distinguish between -0.0 and +0.0. */
+# define MINUSZERO 1
+#endif
+
+/* Define 1 for ANSI C atan2() function
+ See atan.c and clog.c. */
+#define ANSIC 1
diff --git a/ao-tools/tests/reset b/ao-tools/tests/reset
index a32c8bec..65b72803 100644
--- a/ao-tools/tests/reset
+++ b/ao-tools/tests/reset
@@ -1,5 +1,7 @@
# reset
C D R
-C D R
-C D R
+C D .
+C D .
+C D .
+C D .
C D R