--- /dev/null
+MODULES=gtk+-2.0 libglade-2.0
+INCLUDES=$(shell pkg-config --cflags $(MODULES)) -I..
+WARN= -Wall -Wpointer-arith -Wmissing-prototypes -Wmissing-declarations -Wnested-externs -fno-strict-aliasing
+CFLAGS=$(INCLUDES) -O0 -g $(WARN)
+LIBS=$(shell pkg-config --libs $(MODULES)) -lm
+
+SRC = \
+ aoview_main.c \
+ aoview_dev.c \
+ aoview_dev_dialog.c \
+ aoview_serial.c \
+ aoview_monitor.c \
+ aoview_state.c \
+ aoview_convert.c
+
+INC = \
+ aoview.h
+
+OBJ = \
+ $(SRC:.c=.o)
+
+PROG = aoview
+
+$(PROG): $(OBJ)
+ $(CC) $(CFLAGS) -o $@ $(OBJ) $(LIBS)
+
+$(OBJ): $(INC)
+
+clean:
+ rm -f $(OBJ) $(PROG)
--- /dev/null
+<?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">300</property>
+ <property name="height_request">300</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>
+ </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>
+ </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">7</property>
+ <property name="n_columns">2</property>
+ <child>
+ <widget class="GtkLabel" id="altitude_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Altitude</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Velocity</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Acceleration</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Latitude</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Longitude</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Range</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Direction</property>
+ </widget>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="altitude_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">0m</property>
+ <property name="selectable">True</property>
+ <property name="single_line_mode">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="vel_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">150m/s</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="accel_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">50m/s²</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="lat_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">45° 31' 24''</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="lon_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">122° 40' 34''W</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="range_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">500m</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="direction_value">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">45°</property>
+ <property name="selectable">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</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="connect_button">
+ <property name="label" translatable="yes">gtk-connect</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>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">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">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>
--- /dev/null
+/*
+ * 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_
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#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>
+
+struct usbdev {
+ char *sys;
+ char *tty;
+ char *manufacturer;
+ char *product;
+ char *serial;
+ int idProduct;
+ int idVendor;
+};
+
+struct aostate {
+ char callsign[16];
+ int serial;
+ int rssi;
+ char state[16];
+ int tick;
+ int accel;
+ int pres;
+ int temp;
+ int batt;
+ int drogue;
+ int main;
+ int nsat;
+ int locked;
+ struct {
+ int hour;
+ int minute;
+ int second;
+ } gps_time;
+ double lat;
+ double lon;
+ int alt;
+};
+
+void
+aoview_monitor_disconnect(void);
+
+void
+aoview_monitor_connect(char *tty);
+
+struct aoview_serial *
+aoview_serial_open(const char *tty);
+
+void
+aoview_serial_close(struct aoview_serial *serial);
+
+void
+aoview_serial_set_callback(struct aoview_serial *serial,
+ GSourceFunc func,
+ gpointer data,
+ GDestroyNotify notify);
+
+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);
+
+int
+aoview_usb_scan(struct usbdev ***devs_ret);
+
+void
+aoview_usbdev_free(struct usbdev *usbdev);
+
+void
+aoview_state_notify(struct aostate *state);
+
+void
+aoview_state_init(GladeXML *xml);
+
+int16_t
+aoview_pres_to_altitude(int16_t pres);
+
+int16_t
+aoview_altitude_to_pres(int16_t alt);
+
+#endif /* _AOVIEW_H_ */
--- /dev/null
+/*
+ * 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[2048] = {
+#include "../altitude.h"
+};
+
+int16_t
+aoview_pres_to_altitude(int16_t pres)
+{
+ pres = pres >> 4;
+ if (pres < 0) pres = 0;
+ if (pres > 2047) pres = 2047;
+ return altitude_table[pres];
+}
+
+int16_t
+aoview_altitude_to_pres(int16_t alt)
+{
+ int16_t pres;
+
+ for (pres = 0; pres < 2047; pres++)
+ if (altitude_table[pres] <= alt)
+ break;
+ return pres << 4;
+}
--- /dev/null
+/*
+ * 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 <ctype.h>
+#include <dirent.h>
+
+static char *
+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;
+}
+
+static char *
+load_string(char *dir, char *file)
+{
+ char *full = 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 char *
+basename(char *file)
+{
+ char *b;
+
+ b = strrchr(file, '/');
+ if (!b)
+ return file;
+ return b + 1;
+}
+
+static int
+dir_filter_tty(const struct dirent *d)
+{
+ return strncmp(d->d_name, "tty:", 4) == 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;
+ int ntty;
+ char *tty;
+
+ base = 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 = fullname(sys, endpoint_base);
+ ntty = scandir(endpoint_full, &namelist,
+ dir_filter_tty,
+ alphasort);
+ free(endpoint_full);
+ if (ntty) {
+ tty = fullname("/dev", namelist[0]->d_name + 4);
+ free(namelist);
+ return tty;
+ }
+ }
+ }
+ return NULL;
+}
+
+static struct usbdev *
+usb_scan_device(char *sys)
+{
+ struct usbdev *usbdev;
+
+ usbdev = calloc(1, sizeof (struct usbdev));
+ if (!usbdev)
+ return NULL;
+ usbdev->sys = strdup(sys);
+ usbdev->manufacturer = load_string(sys, "manufacturer");
+ usbdev->product = load_string(sys, "product");
+ usbdev->serial = load_string(sys, "serial");
+ usbdev->idProduct = load_hex(sys, "idProduct");
+ usbdev->idVendor = load_hex(sys, "idVendor");
+ usbdev->tty = usb_tty(sys);
+ return usbdev;
+}
+
+void
+aoview_usbdev_free(struct usbdev *usbdev)
+{
+ free(usbdev->sys);
+ free(usbdev->manufacturer);
+ free(usbdev->product);
+ free(usbdev->serial);
+ 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;
+ return 0;
+ }
+ return 1;
+}
+
+int
+aoview_usb_scan(struct usbdev ***devs_ret)
+{
+ int n;
+ int ndev = 0;
+ int e;
+ struct dirent **ents;
+ char *dir;
+ struct usbdev **devs = NULL;
+ struct usbdev *dev;
+
+ n = scandir (USB_DEVICES, &ents,
+ dir_filter_dev,
+ alphasort);
+ if (!n)
+ return 0;
+ for (e = 0; e < n; e++) {
+ dir = fullname(USB_DEVICES, ents[e]->d_name);
+ dev = usb_scan_device(dir);
+ free(dir);
+ if (dev->idVendor == 0xfffe && dev->tty) {
+ if (devs)
+ devs = realloc(devs, ndev + 1 * sizeof (struct usbdev *));
+ else
+ devs = malloc (sizeof (struct usbdev *));
+ devs[ndev++] = dev;
+ }
+ }
+ free(ents);
+ *devs_ret = devs;
+ return ndev;
+}
--- /dev/null
+/*
+ * 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 GtkTreeViewColumn *
+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;
+}
+
+
+
+static void
+aoview_dev_dialog_map(GtkWidget *widget, gpointer data)
+{
+ GtkTreeView *dev_list = data;
+ GtkListStore *list_store;
+ GtkTreeIter iter;
+ int ndev, n;
+ struct usbdev **devs;
+
+ list_store = gtk_list_store_new(3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ ndev = aoview_usb_scan(&devs);
+ for (n = 0; n < ndev; n++) {
+ gtk_list_store_append(list_store, &iter);
+ gtk_list_store_set(list_store, &iter,
+ 0, devs[n]->product,
+ 1, devs[n]->serial,
+ 2, devs[n]->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);
+}
+
+static void
+aoview_dev_selected(GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *string;
+ gtk_tree_model_get(model, iter,
+ 2, &string,
+ -1);
+ aoview_monitor_connect(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();
+}
+
+#define _(a) a
+
+void
+aoview_dev_dialog_init(GladeXML *xml)
+{
+ GtkTreeView *dev_list;
+ GtkWidget *connect_button;
+ GtkTreeSelection *dev_selection;
+ GtkWidget *ao_disconnect;
+
+ 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);
+
+ add_plain_text_column(dev_list, _("Product"), 0, 16);
+ add_plain_text_column(dev_list, _("Serial"), 1, 8);
+ 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);
+}
--- /dev/null
+/*
+ * 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 usage(void) {
+ printf("aoview [--device|-d device_file]");
+ exit(1);
+}
+
+static void destroy_event(GtkWidget *widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+int main(int argc, char **argv)
+{
+ GladeXML *xml = NULL;
+ GtkWidget *mainwindow;
+ char *device = NULL;
+
+ static struct option long_options[] = {
+ { "device", 1, 0, 'd'},
+ { 0, 0, 0, 0 }
+ };
+ for (;;) {
+ int c, temp;
+
+ c = getopt_long_only(argc, argv, "d:", long_options, &temp);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ device = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ gtk_init(&argc, &argv);
+ glade_init();
+
+ xml = glade_xml_new("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");
+ g_signal_connect (G_OBJECT(mainwindow), "destroy",
+ G_CALLBACK(destroy_event), NULL);
+
+ aoview_dev_dialog_init(xml);
+
+ aoview_state_init(xml);
+
+ gtk_main();
+
+ return 0;
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
+
+static void
+aoview_parse_string(char *target, int len, char *source)
+{
+ strncpy(target, source, len-1);
+ target[len-1] = '\0';
+}
+
+static void
+aoview_parse_int(int *target, char *source)
+{
+ *target = strtol(source, NULL, 0);
+}
+
+static void
+aoview_parse_pos(double *target, char *source)
+{
+ int deg;
+ double min;
+ char dir;
+ double r;
+
+ if (sscanf(source, "%d°%lf'%c", °, &min, &dir) != 3) {
+ *target = 0;
+ return;
+ }
+ r = deg + min / 60.0;
+ if (dir == 'S' || dir == 'W')
+ r = -r;
+ *target = r;
+}
+
+static void
+aoview_monitor_parse(char *line)
+{
+ char *saveptr;
+ char *words[64];
+ int nword;
+ struct aostate state;
+
+ printf ("%s\n", line);
+ for (nword = 0; nword < 64; nword++) {
+ words[nword] = strtok_r(line, " \t\n", &saveptr);
+ line = NULL;
+ if (words[nword] == NULL)
+ break;
+ }
+ if (nword < 26)
+ return;
+ if (strcmp(words[0], "CALL") != 0)
+ return;
+ aoview_parse_string(state.callsign, sizeof (state.callsign), words[1]);
+ aoview_parse_int(&state.serial, words[3]);
+ aoview_parse_int(&state.rssi, words[5]);
+ aoview_parse_string(state.state, sizeof (state.state), words[9]);
+ aoview_parse_int(&state.tick, words[10]);
+ aoview_parse_int(&state.accel, words[12]);
+ aoview_parse_int(&state.pres, words[14]);
+ aoview_parse_int(&state.temp, words[16]);
+ aoview_parse_int(&state.batt, words[18]);
+ aoview_parse_int(&state.drogue, words[20]);
+ aoview_parse_int(&state.main, words[22]);
+ if (strcmp (words[26], "unlocked") != 0 && nword >= 29) {
+ sscanf(words[26], "%d:%d:%d", &state.gps_time.hour, &state.gps_time.minute, &state.gps_time.second);
+ aoview_parse_pos(&state.lat, words[27]);
+ aoview_parse_pos(&state.lon, words[28]);
+ sscanf(words[29], "%dm", &state.alt);
+ } else {
+ state.gps_time.hour = state.gps_time.minute = state.gps_time.second = 0;
+ state.lat = state.lon = 0;
+ state.alt = 0;
+ }
+ aoview_state_notify(&state);
+}
+
+static gboolean
+aoview_monitor_callback(void *user_data)
+{
+ int c;
+
+ if (!monitor_serial)
+ return FALSE;
+
+ for (;;) {
+ c = aoview_serial_getc(monitor_serial);
+ if (c == -1)
+ break;
+ if (c == '\r')
+ continue;
+ if (c == '\n') {
+ monitor_line[monitor_pos] = '\0';
+ if (monitor_pos)
+ aoview_monitor_parse(monitor_line);
+ monitor_pos = 0;
+ } else if (monitor_pos < MONITOR_LEN)
+ monitor_line[monitor_pos++] = c;
+ }
+ return TRUE;
+}
+
+void
+aoview_monitor_connect(char *tty)
+{
+ aoview_monitor_disconnect();
+ monitor_serial = aoview_serial_open(tty);
+ aoview_serial_set_callback(monitor_serial,
+ aoview_monitor_callback,
+ monitor_serial,
+ NULL);
+}
--- /dev/null
+/*
+ * 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;
+ 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 (callback && (revents & G_IO_IN))
+ (*callback)(user_data);
+ 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) {
+ free (serial);
+ 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);
+ return serial;
+}
+
+void
+aoview_serial_close(struct aoview_serial *serial)
+{
+ g_source_remove_poll(&serial->source, &serial->poll_fd);
+ g_source_destroy(&serial->source);
+ g_source_unref(&serial->source);
+}
+
+void
+aoview_serial_set_callback(struct aoview_serial *serial,
+ GSourceFunc func,
+ gpointer data,
+ GDestroyNotify notify)
+{
+ g_source_set_callback(&serial->source, func, data, notify);
+}
--- /dev/null
+/*
+ * 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 int pad_pres;
+static int pad_accel;
+
+static int pad_pres_total;
+static int pad_accel_total;
+static double pad_lat_total;
+static double pad_lon_total;
+static int pad_alt_total;
+static int npad;
+static int prev_tick;
+static double prev_accel;
+static double velocity;
+static double pad_lat;
+static double pad_lon;
+static double pad_alt;
+
+#define NUM_PAD_SAMPLES 50
+
+
+
+static void
+aoview_great_circle (double start_lat, double start_lon,
+ double end_lat, double end_lon,
+ double *dist, double *bearing)
+{
+ double rad = M_PI / 180;
+ double earth_radius = 6371.2;
+ double a = (90 - start_lat) * rad;
+ double b = (90 - end_lat) * rad;
+ double phi = (end_lon - start_lon) * rad;
+ double cosr = cos(a) * cos(b) + sin(a) * sin(b) * cos(phi);
+ double r = acos(cosr);
+ double rdist = earth_radius * r;
+ double sinth = sin(phi) * sin(b) / sin(r);
+ double th = asin(sinth) / rad;
+ *dist = rdist;
+ *bearing = th;
+}
+
+void
+aoview_state_notify(struct aostate *state)
+{
+ int altitude;
+ double accel;
+ double velocity_change;
+ int ticks;
+ double dist;
+ double bearing;
+
+ if (!strcmp(state->state, "pad")) {
+ if (npad < NUM_PAD_SAMPLES) {
+ pad_accel_total += state->accel;
+ pad_pres_total += state->pres;
+ pad_lat_total += state->lat;
+ pad_lon_total += state->lon;
+ pad_alt_total += state->alt;
+ npad++;
+ velocity = 0;
+ }
+ if (npad <= NUM_PAD_SAMPLES) {
+ pad_pres = pad_pres_total / npad;
+ pad_accel = pad_accel_total / npad;
+ pad_lat = pad_lat_total / npad;
+ pad_lon = pad_lon_total / npad;
+ pad_alt = pad_alt_total / npad;
+ }
+ }
+ altitude = aoview_pres_to_altitude(state->pres) - aoview_pres_to_altitude(pad_pres);
+ accel = (pad_accel - state->accel) / 264.8 * 9.80665;
+ velocity_change = (accel + prev_accel) / 2.0;
+ ticks = state->tick - prev_tick;
+ velocity -= velocity_change * (ticks / 100.0);
+
+ prev_accel = accel;
+ prev_tick = state->tick;
+ printf ("Pad altitude: %dm\n", aoview_pres_to_altitude(pad_pres));
+ printf ("AGL: %dm\n", altitude);
+ printf ("Acceleration: %gm/s²\n", accel);
+ printf ("Velocity: %gm/s\n", velocity);
+ printf ("Lat: %g\n", state->lat);
+ printf ("Lon: %g\n", state->lon);
+ printf ("GPS alt: %d\n", state->alt);
+ aoview_great_circle(pad_lat, pad_lon, state->lat, state->lon,
+ &dist, &bearing);
+ printf ("Course: %gkm %g°\n", dist, bearing);
+}
+
+void
+aoview_state_init(GladeXML *xml)
+{
+}
--- /dev/null
+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