summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith Packard <keithp@keithp.com>2010-05-17 21:30:57 -0700
committerKeith Packard <keithp@keithp.com>2010-05-17 21:30:57 -0700
commit563a9dcdfef42718370c49f16cc2271642b3e055 (patch)
tree332c5cc67f8f4fa737691c98c80c9c70b1406dac
parentcc002c0a43a02845ba67d1a61828be382f307b2e (diff)
Finish basic flight monitoring UI with voice using FreeTTS
This captures telemetry data to log files and presents flight status information in audio form using FreeTTS. Signed-off-by: Keith Packard <keithp@keithp.com>
-rw-r--r--ao-tools/altosui/AltosFile.java37
-rw-r--r--ao-tools/altosui/AltosLog.java104
-rw-r--r--ao-tools/altosui/AltosPreferences.java117
-rw-r--r--ao-tools/altosui/AltosState.java9
-rw-r--r--ao-tools/altosui/AltosUI.java152
-rw-r--r--ao-tools/altosui/AltosVoice.java69
-rw-r--r--ao-tools/altosui/Makefile5
-rw-r--r--ao-tools/altosui/voices.txt1
8 files changed, 415 insertions, 79 deletions
diff --git a/ao-tools/altosui/AltosFile.java b/ao-tools/altosui/AltosFile.java
new file mode 100644
index 00000000..c7ee8679
--- /dev/null
+++ b/ao-tools/altosui/AltosFile.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2010 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.
+ */
+
+package altosui;
+
+import java.lang.*;
+import java.io.File;
+import java.util.*;
+import altosui.AltosTelemetry;
+import altosui.AltosPreferences;
+
+class AltosFile extends File {
+ public AltosFile(AltosTelemetry telem) {
+ super (AltosPreferences.logdir(),
+ String.format("%04d-%02d-%02d-serial-%03d-flight-%03d.%s",
+ Calendar.getInstance().get(Calendar.YEAR),
+ Calendar.getInstance().get(Calendar.MONTH),
+ Calendar.getInstance().get(Calendar.DAY_OF_MONTH),
+ telem.serial,
+ telem.flight,
+ "telem"));
+ }
+}
diff --git a/ao-tools/altosui/AltosLog.java b/ao-tools/altosui/AltosLog.java
new file mode 100644
index 00000000..ec868b9c
--- /dev/null
+++ b/ao-tools/altosui/AltosLog.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2010 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.
+ */
+
+package altosui;
+
+import java.io.*;
+import java.lang.*;
+import java.util.*;
+import java.text.ParseException;
+import java.util.concurrent.LinkedBlockingQueue;
+import altosui.AltosSerial;
+import altosui.AltosFile;
+
+/*
+ * This creates a thread to capture telemetry data and write it to
+ * a log file
+ */
+class AltosLog implements Runnable {
+
+ LinkedBlockingQueue<String> input_queue;
+ LinkedBlockingQueue<String> pending_queue;
+ int serial;
+ int flight;
+ FileWriter log_file;
+ Thread log_thread;
+
+ void close() throws IOException {
+ if (log_file != null)
+ log_file.close();
+ }
+
+ boolean open (AltosTelemetry telem) throws IOException {
+ AltosFile a = new AltosFile(telem);
+
+ log_file = new FileWriter(a, true);
+ if (log_file != null) {
+ while (!pending_queue.isEmpty()) {
+ try {
+ String s = pending_queue.take();
+ log_file.write(s);
+ log_file.write('\n');
+ } catch (InterruptedException ie) {
+ }
+ }
+ log_file.flush();
+ }
+ return log_file != null;
+ }
+
+ public void run () {
+ try {
+ for (;;) {
+ String line = input_queue.take();
+ try {
+ AltosTelemetry telem = new AltosTelemetry(line);
+ if (telem.serial != serial || telem.flight != flight || log_file == null) {
+ close();
+ serial = telem.serial;
+ flight = telem.flight;
+ open(telem);
+ }
+ } catch (ParseException pe) {
+ }
+ if (log_file != null) {
+ log_file.write(line);
+ log_file.write('\n');
+ log_file.flush();
+ } else
+ pending_queue.put(line);
+ }
+ } catch (InterruptedException ie) {
+ } catch (IOException ie) {
+ }
+ try {
+ close();
+ } catch (IOException ie) {
+ }
+ }
+
+ public AltosLog (AltosSerial s) {
+ pending_queue = new LinkedBlockingQueue<String> ();
+ input_queue = new LinkedBlockingQueue<String> ();
+ s.add_monitor(input_queue);
+ serial = -1;
+ flight = -1;
+ log_file = null;
+ log_thread = new Thread(this);
+ log_thread.start();
+ }
+}
diff --git a/ao-tools/altosui/AltosPreferences.java b/ao-tools/altosui/AltosPreferences.java
new file mode 100644
index 00000000..0296d935
--- /dev/null
+++ b/ao-tools/altosui/AltosPreferences.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright © 2010 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.
+ */
+
+package altosui;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.awt.Component;
+import javax.swing.*;
+import javax.swing.filechooser.FileSystemView;
+
+class AltosPreferences {
+ static Preferences preferences;
+
+ /* logdir preference name */
+ final static String logdirPreference = "LOGDIR";
+
+ /* Default logdir is ~/TeleMetrum */
+ final static String logdirName = "TeleMetrum";
+
+ /* UI Component to pop dialogs up */
+ static Component component;
+
+ /* Log directory */
+ static File logdir;
+
+ public static void init(Component ui) {
+ preferences = Preferences.userRoot().node("/org/altusmetrum/altosui");
+
+ component = ui;
+
+ /* Initialize logdir from preferences */
+ String logdir_string = preferences.get(logdirPreference, null);
+ if (logdir_string != null)
+ logdir = new File(logdir_string);
+ else {
+ /* Use the file system view default directory */
+ logdir = new File(FileSystemView.getFileSystemView().getDefaultDirectory(), logdirName);
+ if (!logdir.exists())
+ logdir.mkdirs();
+ }
+ }
+
+ static void flush_preferences() {
+ try {
+ preferences.flush();
+ } catch (BackingStoreException ee) {
+ JOptionPane.showMessageDialog(component,
+ preferences.absolutePath(),
+ "Cannot save prefernces",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ public static void set_logdir(File new_logdir) {
+ logdir = new_logdir;
+ synchronized (preferences) {
+ preferences.put(logdirPreference, logdir.getPath());
+ flush_preferences();
+ }
+ }
+
+ private static boolean check_dir(File dir) {
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ JOptionPane.showMessageDialog(component,
+ dir.getName(),
+ "Cannot create directory",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ } else if (!dir.isDirectory()) {
+ JOptionPane.showMessageDialog(component,
+ dir.getName(),
+ "Is not a directory",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ return true;
+ }
+
+ /* Configure the log directory. This is where all telemetry and eeprom files
+ * will be written to, and where replay will look for telemetry files
+ */
+ public static void ConfigureLog() {
+ JFileChooser logdir_chooser = new JFileChooser(logdir.getParentFile());
+
+ logdir_chooser.setDialogTitle("Configure Data Logging Directory");
+ logdir_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+
+ if (logdir_chooser.showDialog(component, "Select Directory") == JFileChooser.APPROVE_OPTION) {
+ File dir = logdir_chooser.getSelectedFile();
+ if (check_dir(dir))
+ set_logdir(dir);
+ }
+ }
+
+ public static File logdir() {
+ return logdir;
+ }
+}
diff --git a/ao-tools/altosui/AltosState.java b/ao-tools/altosui/AltosState.java
index 192011d0..59a1999e 100644
--- a/ao-tools/altosui/AltosState.java
+++ b/ao-tools/altosui/AltosState.java
@@ -29,7 +29,7 @@ public class AltosState {
/* derived data */
- double report_time;
+ long report_time;
double time_change;
int tick;
@@ -66,11 +66,6 @@ public class AltosState {
int speak_tick;
double speak_altitude;
- static double
- aoview_time()
- {
- return System.currentTimeMillis() / 1000.0;
- }
void init (AltosTelemetry cur, AltosState prev_state) {
int i;
@@ -82,7 +77,7 @@ public class AltosState {
ground_altitude = AltosConvert.cc_barometer_to_altitude(data.ground_pres);
height = AltosConvert.cc_barometer_to_altitude(data.flight_pres) - ground_altitude;
- report_time = aoview_time();
+ report_time = System.currentTimeMillis();
accel_counts_per_mss = ((data.accel_minus_g - data.accel_plus_g) / 2.0) / 9.80665;
acceleration = (data.ground_accel - data.flight_accel) / accel_counts_per_mss;
diff --git a/ao-tools/altosui/AltosUI.java b/ao-tools/altosui/AltosUI.java
index 2952fcc0..43c40799 100644
--- a/ao-tools/altosui/AltosUI.java
+++ b/ao-tools/altosui/AltosUI.java
@@ -34,6 +34,9 @@ import altosui.AltosSerialMonitor;
import altosui.AltosTelemetry;
import altosui.AltosState;
import altosui.AltosDeviceDialog;
+import altosui.AltosPreferences;
+import altosui.AltosLog;
+import altosui.AltosVoice;
class AltosFlightStatusTableModel extends AbstractTableModel {
private String[] columnNames = {"Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
@@ -128,7 +131,8 @@ public class AltosUI extends JFrame {
private AltosFlightInfoTableModel[] flightInfoModel;
private JTable[] flightInfo;
- private AltosSerial serialLine;
+ private AltosSerial serial_line;
+ private AltosLog altos_log;
private Box[] ibox;
private Box vbox;
private Box hbox;
@@ -137,11 +141,15 @@ public class AltosUI extends JFrame {
private Font infoLabelFont = new Font("SansSerif", Font.PLAIN, 14);
private Font infoValueFont = new Font("Monospaced", Font.PLAIN, 14);
+ public AltosVoice voice = new AltosVoice();
+
public AltosUI() {
String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
Object[][] statusData = { { "0", "pad", "-50", "0" } };
+ AltosPreferences.init(this);
+
vbox = Box.createVerticalBox();
this.add(vbox);
@@ -188,7 +196,8 @@ public class AltosUI extends JFrame {
createMenu();
- serialLine = new AltosSerial();
+ serial_line = new AltosSerial();
+ altos_log = new AltosLog(serial_line);
int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
this.setSize(new Dimension (infoValueMetrics.charWidth('0') * 6 * 20,
statusHeight * 4 + infoHeight * 17));
@@ -333,63 +342,76 @@ public class AltosUI extends JFrame {
info_finish();
}
- /* User Preferences */
- Preferences altosui_preferences = Preferences.userNodeForPackage(this.getClass());
-
- /* Log directory */
- private File logdir = null;
+ class IdleThread extends Thread {
- /* logdir preference name */
- final static String logdirPreference = "LOGDIR";
+ private AltosState state;
- /* Default logdir is ~/TeleMetrum */
- final static String logdirName = "TeleMetrum";
-
- /* Initialize logdir from preferences */
- {
- String logdir_string = altosui_preferences.get(logdirPreference, null);
- if (logdir_string != null)
- logdir = new File(logdir_string);
- else
- /* a hack -- make the file chooser tell us what the default directory
- * would be and stick our logdir in a subdirectory of that.
- */
- logdir = new File(new JFileChooser().getCurrentDirectory(), logdirName);
- }
+ public void run () {
+ int reported_landing = 0;
- private void set_logdir(File new_logdir) {
- logdir = new_logdir;
- System.out.printf("Set logdir to %s\n", logdir.toString());
- synchronized (altosui_preferences) {
- altosui_preferences.put(logdirPreference, logdir.getPath());
+ state = null;
try {
- altosui_preferences.flush();
- } catch (BackingStoreException ee) {
- JOptionPane.showMessageDialog(AltosUI.this,
- altosui_preferences.absolutePath(),
- "Cannot save prefernces",
- JOptionPane.ERROR_MESSAGE);
+ for (;;) {
+ Thread.sleep(10000);
+ if (state == null)
+ continue;
+
+ /* reset the landing count once we hear about a new flight */
+ if (state.state < AltosTelemetry.ao_flight_drogue)
+ reported_landing = 0;
+
+ /* Shut up once the rocket is on the ground */
+ if (reported_landing > 2)
+ continue;
+
+ /* If the rocket isn't on the pad, then report height */
+ if (state.state > AltosTelemetry.ao_flight_pad) {
+ voice.speak(String.format("%d meters", (int) (state.height + 0.5)));
+ }
+
+ /* If the rocket is coming down, check to see if it has landed;
+ * either we've got a landed report or we haven't heard from it in
+ * a long time
+ */
+ if (!state.ascent &&
+ (System.currentTimeMillis() - state.report_time > 10000 ||
+ state.state == AltosTelemetry.ao_flight_landed))
+ {
+ if (Math.abs(state.baro_speed) < 20 && state.height < 100)
+ voice.speak("rocket landed safely");
+ else
+ voice.speak("rocket may have crashed");
+ if (state.gps != null)
+ voice.speak(String.format("bearing %d degrees, range %d meters",
+ (int) (state.from_pad.bearing + 0.5),
+ (int) (state.from_pad.distance + 0.5)));
+ ++reported_landing;
+ }
+ }
+ } catch (InterruptedException ie) {
}
}
+
+ public void notice(AltosState new_state) {
+ state = new_state;
+ }
}
- private boolean check_dir(File dir) {
- if (!dir.exists()) {
- if (!dir.mkdirs()) {
- JOptionPane.showMessageDialog(AltosUI.this,
- dir.getName(),
- "Cannot create directory",
- JOptionPane.ERROR_MESSAGE);
- return false;
+ private void tell(AltosState state, AltosState old_state) {
+ if (old_state == null || old_state.state != state.state) {
+ voice.speak(state.data.state);
+ switch (state.state) {
+ case AltosTelemetry.ao_flight_fast:
+ voice.speak(String.format("max speed %d meters per second",
+ (int) (state.max_speed + 0.5)));
+ break;
+ case AltosTelemetry.ao_flight_drogue:
+ voice.speak(String.format("max height %d meters",
+ (int) (state.max_height + 0.5)));
+ break;
}
- } else if (!dir.isDirectory()) {
- JOptionPane.showMessageDialog(AltosUI.this,
- dir.getName(),
- "Is not a directory",
- JOptionPane.ERROR_MESSAGE);
- return false;
}
- return true;
+ old_state = state;
}
class DisplayThread extends Thread {
@@ -402,16 +424,22 @@ public class AltosUI extends JFrame {
public void run() {
String line;
AltosState state = null;
+ AltosState old_state = null;
+ IdleThread idle_thread = new IdleThread();
info_reset();
info_finish();
+ idle_thread.start();
try {
while ((line = read()) != null) {
try {
AltosTelemetry t = new AltosTelemetry(line);
+ old_state = state;
state = new AltosState(t, state);
update(state);
show(state);
+ tell(state, old_state);
+ idle_thread.notice(state);
} catch (ParseException pp) {
System.out.printf("Parse error on %s\n", line);
System.out.println("exception " + pp);
@@ -420,6 +448,7 @@ public class AltosUI extends JFrame {
} catch (InterruptedException ee) {
} finally {
close();
+ idle_thread.interrupt();
}
}
}
@@ -450,8 +479,8 @@ public class AltosUI extends JFrame {
if (device != null) {
try {
- serialLine.connect(device.tty);
- DeviceThread thread = new DeviceThread(serialLine);
+ serial_line.connect(device.tty);
+ DeviceThread thread = new DeviceThread(serial_line);
run_display(thread);
} catch (FileNotFoundException ee) {
JOptionPane.showMessageDialog(AltosUI.this,
@@ -556,7 +585,7 @@ public class AltosUI extends JFrame {
logfile_chooser.setDialogTitle("Select Telemetry File");
logfile_chooser.setFileFilter(new FileNameExtensionFilter("Telemetry file", "telem"));
- logfile_chooser.setCurrentDirectory(logdir);
+ logfile_chooser.setCurrentDirectory(AltosPreferences.logdir());
int returnVal = logfile_chooser.showOpenDialog(AltosUI.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
@@ -583,23 +612,6 @@ public class AltosUI extends JFrame {
private void SaveFlightData() {
}
- /* Configure the log directory. This is where all telemetry and eeprom files
- * will be written to, and where replay will look for telemetry files
- */
- private void ConfigureLog() {
- JFileChooser logdir_chooser = new JFileChooser();
-
- logdir_chooser.setDialogTitle("Configure Data Logging Directory");
- logdir_chooser.setCurrentDirectory(logdir.getParentFile());
- logdir_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
-
- if (logdir_chooser.showDialog(AltosUI.this, "Select Directory") == JFileChooser.APPROVE_OPTION) {
- File dir = logdir_chooser.getSelectedFile();
- if (check_dir(dir))
- set_logdir(dir);
- }
- }
-
/* Create the AltosUI menus
*/
private void createMenu() {
@@ -681,7 +693,7 @@ public class AltosUI extends JFrame {
item = new JMenuItem("Configure Log",KeyEvent.VK_C);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
- ConfigureLog();
+ AltosPreferences.ConfigureLog();
}
});
menu.add(item);
diff --git a/ao-tools/altosui/AltosVoice.java b/ao-tools/altosui/AltosVoice.java
new file mode 100644
index 00000000..e4ea99a2
--- /dev/null
+++ b/ao-tools/altosui/AltosVoice.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2010 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.
+ */
+
+package altosui;
+
+import com.sun.speech.freetts.Voice;
+import com.sun.speech.freetts.VoiceManager;
+import com.sun.speech.freetts.audio.JavaClipAudioPlayer;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosVoice implements Runnable {
+ VoiceManager voice_manager;
+ Voice voice;
+ LinkedBlockingQueue<String> phrases;
+ Thread thread;
+
+ final static String voice_name = "kevin16";
+
+ public void run() {
+ try {
+ for (;;) {
+ String s = phrases.take();
+ voice.speak(s);
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+ public void speak(String s) {
+ try {
+ if (voice != null)
+ phrases.put(s);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public AltosVoice () {
+ voice_manager = VoiceManager.getInstance();
+ voice = voice_manager.getVoice(voice_name);
+ if (voice != null) {
+ voice.allocate();
+ phrases = new LinkedBlockingQueue<String> ();
+ thread = new Thread(this);
+ thread.start();
+ speak("Rocket Flight Monitor Ready");
+ } else {
+ System.out.printf("Voice manager failed to open %s\n", voice_name);
+ Voice[] voices = voice_manager.getVoices();
+ System.out.printf("Available voices:\n");
+ for (int i = 0; i < voices.length; i++) {
+ System.out.println(" " + voices[i].getName()
+ + " (" + voices[i].getDomain() + " domain)");
+ }
+ }
+ }
+}
diff --git a/ao-tools/altosui/Makefile b/ao-tools/altosui/Makefile
index b9f025da..57c889b8 100644
--- a/ao-tools/altosui/Makefile
+++ b/ao-tools/altosui/Makefile
@@ -1,6 +1,6 @@
.SUFFIXES: .java .class
-CLASSPATH=..:/usr/share/java/*
+CLASSPATH=..:/usr/share/java/*:/home/keithp/src/freetts/freetts-1.2.2
CLASSFILES=\
AltosConvert.class \
AltosFile.class \
@@ -16,7 +16,8 @@ CLASSFILES=\
AltosUI.class \
AltosDevice.class \
AltosDeviceLinux.class \
- AltosDeviceDialog.class
+ AltosDeviceDialog.class \
+ AltosVoice.class
JAVAFLAGS=-Xlint:unchecked
diff --git a/ao-tools/altosui/voices.txt b/ao-tools/altosui/voices.txt
new file mode 100644
index 00000000..e8825fc3
--- /dev/null
+++ b/ao-tools/altosui/voices.txt
@@ -0,0 +1 @@
+com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory