diff options
Diffstat (limited to 'ao-view')
| -rw-r--r-- | ao-view/.gitignore | 4 | ||||
| -rw-r--r-- | ao-view/Makefile.am | 33 | ||||
| -rw-r--r-- | ao-view/aoview.glade | 734 | ||||
| -rw-r--r-- | ao-view/aoview.h | 311 | ||||
| -rw-r--r-- | ao-view/aoview_convert.c | 42 | ||||
| -rw-r--r-- | ao-view/aoview_dev.c | 208 | ||||
| -rw-r--r-- | ao-view/aoview_dev_dialog.c | 168 | ||||
| -rw-r--r-- | ao-view/aoview_eeprom.c | 157 | ||||
| -rw-r--r-- | ao-view/aoview_file.c | 236 | ||||
| -rw-r--r-- | ao-view/aoview_flite.c | 135 | ||||
| -rw-r--r-- | ao-view/aoview_label.c | 73 | ||||
| -rw-r--r-- | ao-view/aoview_log.c | 70 | ||||
| -rw-r--r-- | ao-view/aoview_main.c | 110 | ||||
| -rw-r--r-- | ao-view/aoview_monitor.c | 196 | ||||
| -rw-r--r-- | ao-view/aoview_replay.c | 147 | ||||
| -rw-r--r-- | ao-view/aoview_serial.c | 270 | ||||
| -rw-r--r-- | ao-view/aoview_state.c | 349 | ||||
| -rw-r--r-- | ao-view/aoview_table.c | 83 | ||||
| -rw-r--r-- | ao-view/aoview_util.c | 91 | ||||
| -rw-r--r-- | ao-view/aoview_voice.c | 122 | ||||
| -rw-r--r-- | ao-view/design | 27 | 
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 © 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 <keithp@keithp.com></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"><filename></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", °, &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 | 
