summaryrefslogtreecommitdiff
path: root/ao-view
diff options
context:
space:
mode:
authorKeith Packard <keithp@keithp.com>2009-08-18 12:40:24 -0700
committerKeith Packard <keithp@keithp.com>2009-08-18 12:40:24 -0700
commit9789ca5e8caa9a013e804f307b9da380e147bd75 (patch)
tree0bba9fdba9d761d0ead763ab593bc51d1658a693 /ao-view
parenta5782398d968e7cb11f7203afada7c216f233b3b (diff)
Rename tools to ao-<foo>
Use a consistent prefix to make it easier to remember which programs belong to this package Signed-off-by: Keith Packard <keithp@keithp.com>
Diffstat (limited to 'ao-view')
-rw-r--r--ao-view/.gitignore4
-rw-r--r--ao-view/Makefile.am33
-rw-r--r--ao-view/aoview.glade734
-rw-r--r--ao-view/aoview.h311
-rw-r--r--ao-view/aoview_convert.c42
-rw-r--r--ao-view/aoview_dev.c208
-rw-r--r--ao-view/aoview_dev_dialog.c168
-rw-r--r--ao-view/aoview_eeprom.c157
-rw-r--r--ao-view/aoview_file.c236
-rw-r--r--ao-view/aoview_flite.c135
-rw-r--r--ao-view/aoview_label.c73
-rw-r--r--ao-view/aoview_log.c70
-rw-r--r--ao-view/aoview_main.c110
-rw-r--r--ao-view/aoview_monitor.c196
-rw-r--r--ao-view/aoview_replay.c147
-rw-r--r--ao-view/aoview_serial.c270
-rw-r--r--ao-view/aoview_state.c349
-rw-r--r--ao-view/aoview_table.c83
-rw-r--r--ao-view/aoview_util.c91
-rw-r--r--ao-view/aoview_voice.c122
-rw-r--r--ao-view/design27
21 files changed, 3566 insertions, 0 deletions
diff --git a/ao-view/.gitignore b/ao-view/.gitignore
new file mode 100644
index 00000000..24fbc596
--- /dev/null
+++ b/ao-view/.gitignore
@@ -0,0 +1,4 @@
+*.o
+aoview
+aoview_glade.h
+aoview_flite
diff --git a/ao-view/Makefile.am b/ao-view/Makefile.am
new file mode 100644
index 00000000..e0cd068c
--- /dev/null
+++ b/ao-view/Makefile.am
@@ -0,0 +1,33 @@
+VERSION=$(shell git describe)
+
+AM_CFLAGS=$(GNOME_CFLAGS) $(ALSA_CFLAGS) -I$(top_srcdir)/src -DAOVIEW_VERSION=\"$(VERSION)\" @FLITE_INCS@
+
+bin_PROGRAMS=ao-view
+
+ao_view_LDADD=$(GNOME_LIBS) $(FLITE_LIBS) $(ALSA_LIBS)
+
+ao_view_SOURCES = \
+ aoview_main.c \
+ aoview_dev.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.h
+
+BUILT_SOURCES = aoview_glade.h
+
+CLEANFILES = aoview_glade.h
+
+aoview_glade.h: aoview.glade
+ sed -e 's/"/\\"/g' -e 's/^/"/' -e 's/$$/"/' $< > $@
diff --git a/ao-view/aoview.glade b/ao-view/aoview.glade
new file mode 100644
index 00000000..df08b83c
--- /dev/null
+++ b/ao-view/aoview.glade
@@ -0,0 +1,734 @@
+<?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">550</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="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>
+ </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" 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">0</property>
+ </packing>
+ </child>
+ <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="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" translatable="yes">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" translatable="yes">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="has_separator">False</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" translatable="yes">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" translatable="yes">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-view/aoview.h b/ao-view/aoview.h
new file mode 100644
index 00000000..62d0640b
--- /dev/null
+++ b/ao-view/aoview.h
@@ -0,0 +1,311 @@
+/*
+ * 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 <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gconf/gconf-client.h>
+
+struct usbdev {
+ char *sys;
+ char *tty;
+ char *manufacturer;
+ char *product;
+ char *serial;
+ int idProduct;
+ int idVendor;
+};
+
+struct aogps_time {
+ int hour;
+ int minute;
+ int second;
+};
+
+struct aogps {
+ int nsat;
+ int gps_locked;
+ int gps_connected;
+ struct aogps_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 */
+};
+
+struct aodata {
+ char callsign[16];
+ int serial;
+ int rssi;
+ char state[16];
+ int tick;
+ int accel;
+ int pres;
+ int temp;
+ int batt;
+ int drogue;
+ int main;
+ int flight_accel;
+ int ground_accel;
+ int flight_vel;
+ int flight_pres;
+ int ground_pres;
+ struct aogps gps;
+};
+
+struct aostate {
+ struct aodata data;
+
+ /* derived data */
+
+ struct aodata 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 aogps gps;
+
+ 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_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);
+
+int
+aoview_usb_scan(struct usbdev ***devs_ret);
+
+void
+aoview_usbdev_free(struct usbdev *usbdev);
+
+void
+aoview_state_notify(struct aodata *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);
+
+#endif /* _AOVIEW_H_ */
diff --git a/ao-view/aoview_convert.c b/ao-view/aoview_convert.c
new file mode 100644
index 00000000..02416647
--- /dev/null
+++ b/ao-view/aoview_convert.c
@@ -0,0 +1,42 @@
+/*
+ * 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;
+}
diff --git a/ao-view/aoview_dev.c b/ao-view/aoview_dev.c
new file mode 100644
index 00000000..9b8cc19e
--- /dev/null
+++ b/ao-view/aoview_dev.c
@@ -0,0 +1,208 @@
+/*
+ * 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 *
+load_string(char *dir, char *file)
+{
+ char *full = aoview_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
+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 = aoview_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 = aoview_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 = aoview_fullname("/dev", namelist[0]->d_name + 4);
+ free(namelist);
+ return tty;
+ }
+
+ /* Check for tty/ttyACMx style names
+ */
+ tty_dir = aoview_fullname(endpoint_full, "tty");
+ free(endpoint_full);
+ ntty = scandir(tty_dir, &namelist,
+ dir_filter_tty,
+ alphasort);
+ free (tty_dir);
+ if (ntty > 0) {
+ tty = aoview_fullname("/dev", namelist[0]->d_name);
+ 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;
+ if (c == '.' && n != d->d_name + 1)
+ 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 = aoview_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;
+}
diff --git a/ao-view/aoview_dev_dialog.c b/ao-view/aoview_dev_dialog.c
new file mode 100644
index 00000000..3f92085c
--- /dev/null
+++ b/ao-view/aoview_dev_dialog.c
@@ -0,0 +1,168 @@
+/*
+ * 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 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 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-view/aoview_eeprom.c b/ao-view/aoview_eeprom.c
new file mode 100644
index 00000000..34e2deed
--- /dev/null
+++ b/ao-view/aoview_eeprom.c
@@ -0,0 +1,157 @@
+/*
+ * 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) {
+ 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-view/aoview_file.c b/ao-view/aoview_file.c
new file mode 100644
index 00000000..5288c2f7
--- /dev/null
+++ b/ao-view/aoview_file.c
@@ -0,0 +1,236 @@
+/*
+ * 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 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];
+ struct tm tm;
+ time_t now;
+ char *full;
+ int r;
+
+ if (file->file)
+ return TRUE;
+
+ if (file->failed)
+ return FALSE;
+
+ now = time(NULL);
+ (void) localtime_r(&now, &tm);
+ aoview_mkdir(aoview_file_dir);
+ for (;;) {
+ snprintf(base, sizeof (base), "%04d-%02d-%02d-serial-%03d-flight-%03d.%s",
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ file->serial,
+ file->sequence,
+ file->ext);
+ full = aoview_fullname(aoview_file_dir, base);
+ r = access(full, F_OK);
+ if (r < 0) {
+ 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;
+ }
+ }
+ free(full);
+ file->sequence++;
+ }
+}
+
+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_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-view/aoview_flite.c b/ao-view/aoview_flite.c
new file mode 100644
index 00000000..e1b75898
--- /dev/null
+++ b/ao-view/aoview_flite.c
@@ -0,0 +1,135 @@
+/*
+ * 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_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)
+ {
+ if (err < 0) {
+ snd_pcm_close(alsa_handle);
+ alsa_handle = 0;
+ }
+ }
+ 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();
+ voice = register_cmu_us_kal();
+ 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-view/aoview_label.c b/ao-view/aoview_label.c
new file mode 100644
index 00000000..24313626
--- /dev/null
+++ b/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-view/aoview_log.c b/ao-view/aoview_log.c
new file mode 100644
index 00000000..1b89c28c
--- /dev/null
+++ b/ao-view/aoview_log.c
@@ -0,0 +1,70 @@
+/*
+ * 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_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-view/aoview_main.c b/ao-view/aoview_main.c
new file mode 100644
index 00000000..36a82e0e
--- /dev/null
+++ b/ao-view/aoview_main.c
@@ -0,0 +1,110 @@
+/*
+ * 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();
+}
+
+extern int _Xdebug;
+
+int main(int argc, char **argv)
+{
+ GladeXML *xml = NULL;
+ GtkWidget *mainwindow;
+ char *device = NULL;
+ GtkAboutDialog *about_dialog;
+
+ static struct option long_options[] = {
+ { "device", 1, 0, 'd'},
+ { "sync", 0, 0, 's'},
+ { 0, 0, 0, 0 }
+ };
+ for (;;) {
+ int c, temp;
+
+ c = getopt_long_only(argc, argv, "sd:", long_options, &temp);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'd':
+ device = optarg;
+ break;
+ case 's':
+ _Xdebug = 1;
+ 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_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);
+
+ aoview_voice_speak("rocket flight monitor ready\n");
+
+ gtk_main();
+
+ return 0;
+}
diff --git a/ao-view/aoview_monitor.c b/ao-view/aoview_monitor.c
new file mode 100644
index 00000000..9265a199
--- /dev/null
+++ b/ao-view/aoview_monitor.c
@@ -0,0 +1,196 @@
+/*
+ * 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();
+}
+
+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", &deg, &min, &dir) != 3) {
+ *target = 0;
+ return;
+ }
+ r = deg + min / 60.0;
+ if (dir == 'S' || dir == 'W')
+ r = -r;
+ *target = r;
+}
+
+gboolean
+aoview_monitor_parse(const char *input_line)
+{
+ char *saveptr;
+ char *words[64];
+ int nword;
+ char line_buf[8192], *line;
+ struct aodata data;
+
+ /* avoid smashing our input parameter */
+ strncpy (line_buf, input_line, sizeof (line_buf)-1);
+ line_buf[sizeof(line_buf) - 1] = '\0';
+ line = line_buf;
+ for (nword = 0; nword < 64; nword++) {
+ words[nword] = strtok_r(line, " \t\n", &saveptr);
+ line = NULL;
+ if (words[nword] == NULL)
+ break;
+ }
+ if (nword < 36)
+ return FALSE;
+ if (strcmp(words[0], "CALL") != 0)
+ return FALSE;
+ aoview_parse_string(data.callsign, sizeof (data.callsign), words[1]);
+ aoview_parse_int(&data.serial, words[3]);
+
+ aoview_parse_int(&data.rssi, words[5]);
+ aoview_parse_string(data.state, sizeof (data.state), words[9]);
+ aoview_parse_int(&data.tick, words[10]);
+ aoview_parse_int(&data.accel, words[12]);
+ aoview_parse_int(&data.pres, words[14]);
+ aoview_parse_int(&data.temp, words[16]);
+ aoview_parse_int(&data.batt, words[18]);
+ aoview_parse_int(&data.drogue, words[20]);
+ aoview_parse_int(&data.main, words[22]);
+ aoview_parse_int(&data.flight_accel, words[24]);
+ aoview_parse_int(&data.ground_accel, words[26]);
+ aoview_parse_int(&data.flight_vel, words[28]);
+ aoview_parse_int(&data.flight_pres, words[30]);
+ aoview_parse_int(&data.ground_pres, words[32]);
+ aoview_parse_int(&data.gps.nsat, words[34]);
+ if (strcmp (words[36], "unlocked") == 0) {
+ data.gps.gps_connected = 1;
+ data.gps.gps_locked = 0;
+ data.gps.gps_time.hour = data.gps.gps_time.minute = data.gps.gps_time.second = 0;
+ data.gps.lat = data.gps.lon = 0;
+ data.gps.alt = 0;
+ } else if (nword >= 40) {
+ data.gps.gps_locked = 1;
+ data.gps.gps_connected = 1;
+ sscanf(words[36], "%d:%d:%d", &data.gps.gps_time.hour, &data.gps.gps_time.minute, &data.gps.gps_time.second);
+ aoview_parse_pos(&data.gps.lat, words[37]);
+ aoview_parse_pos(&data.gps.lon, words[38]);
+ sscanf(words[39], "%dm", &data.gps.alt);
+ } else {
+ data.gps.gps_connected = 0;
+ data.gps.gps_locked = 0;
+ data.gps.gps_time.hour = data.gps.gps_time.minute = data.gps.gps_time.second = 0;
+ data.gps.lat = data.gps.lon = 0;
+ data.gps.alt = 0;
+ }
+ if (nword >= 46) {
+ data.gps.gps_extended = 1;
+ sscanf(words[40], "%lfm/s", &data.gps.ground_speed);
+ sscanf(words[41], "%d", &data.gps.course);
+ sscanf(words[42], "%lfm/s", &data.gps.climb_rate);
+ sscanf(words[43], "%lf", &data.gps.hdop);
+ sscanf(words[44], "%d", &data.gps.h_error);
+ sscanf(words[45], "%d", &data.gps.v_error);
+ } else {
+ data.gps.gps_extended = 0;
+ data.gps.ground_speed = 0;
+ data.gps.course = 0;
+ data.gps.climb_rate = 0;
+ data.gps.hdop = 0;
+ data.gps.h_error = 0;
+ data.gps.v_error = 0;
+ }
+ aoview_state_notify(&data);
+ 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);
+ 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;
+ }
+ }
+}
+
+gboolean
+aoview_monitor_connect(char *tty)
+{
+ aoview_monitor_disconnect();
+ monitor_serial = aoview_serial_open(tty);
+ if (!monitor_serial)
+ return FALSE;
+ aoview_table_clear();
+ aoview_state_reset();
+ aoview_serial_set_callback(monitor_serial,
+ aoview_monitor_callback);
+ return TRUE;
+}
diff --git a/ao-view/aoview_replay.c b/ao-view/aoview_replay.c
new file mode 100644
index 00000000..da7b5d6a
--- /dev/null
+++ b/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-view/aoview_serial.c b/ao-view/aoview_serial.c
new file mode 100644
index 00000000..29038b79
--- /dev/null
+++ b/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-view/aoview_state.c b/ao-view/aoview_state.c
new file mode 100644
index 00000000..7efd33b0
--- /dev/null
+++ b/ao-view/aoview_state.c
@@ -0,0 +1,349 @@
+/*
+ * 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 aodata *data, struct aostate *state)
+{
+ int i;
+ double new_height;
+ double height_change;
+ double time_change;
+ 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;
+ state->acceleration = (data->ground_accel - data->flight_accel) / 27.0;
+ state->speed = data->flight_vel / 2700.0;
+ state->temperature = ((data->temp / 32767.0 * 3.3) - 0.5) / 0.01;
+ state->drogue_sense = data->drogue / 32767.0 * 15.0;
+ state->main_sense = data->main / 32767.0 * 15.0;
+ state->battery = data->batt / 32767.0 * 5.0;
+ if (!strcmp(data->state, "pad")) {
+ if (data->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 (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 aodata *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, "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 height", "%d", state->gps_height);
+ 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);
+ }
+ 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-view/aoview_table.c b/ao-view/aoview_table.c
new file mode 100644
index 00000000..93143009
--- /dev/null
+++ b/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 2
+
+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-view/aoview_util.c b/ao-view/aoview_util.c
new file mode 100644
index 00000000..6ea62ac9
--- /dev/null
+++ b/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-view/aoview_voice.c b/ao-view/aoview_voice.c
new file mode 100644
index 00000000..24422df6
--- /dev/null
+++ b/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-view/design b/ao-view/design
new file mode 100644
index 00000000..6ec2ea70
--- /dev/null
+++ b/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