public class AltosDisplayThread extends Thread {
- Frame parent;
- IdleThread idle_thread;
- AltosVoice voice;
- String name;
- int crc_errors;
- AltosStatusTable flightStatus;
- AltosInfoTable flightInfo;
+ Frame parent;
+ IdleThread idle_thread;
+ AltosVoice voice;
+ String name;
+ AltosFlightReader reader;
+ int crc_errors;
+ AltosStatusTable flightStatus;
+ AltosInfoTable flightInfo;
class IdleThread extends Thread {
}
}
- void init() { }
-
- AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; }
-
- void close(boolean interrupted) { }
-
- void update(AltosState state) throws InterruptedException { }
-
boolean tell(AltosState state, AltosState old_state) {
boolean ret = false;
if (old_state == null || old_state.state != state.state) {
try {
for (;;) {
try {
- AltosRecord record = read();
+ AltosRecord record = reader.read();
if (record == null)
break;
old_state = state;
state = new AltosState(record, state);
- update(state);
+ reader.update(state);
show(state, crc_errors);
told = tell(state, old_state);
idle_thread.notice(state, told);
"Telemetry Read Error",
JOptionPane.ERROR_MESSAGE);
} finally {
- close(interrupted);
+ if (!interrupted)
+ idle_thread.report(true);
+ reader.close(interrupted);
idle_thread.interrupt();
try {
idle_thread.join();
}
}
- public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosStatusTable in_status, AltosInfoTable in_info) {
+ public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosStatusTable in_status, AltosInfoTable in_info, AltosFlightReader in_reader) {
parent = in_parent;
voice = in_voice;
flightStatus = in_status;
flightInfo = in_info;
+ reader = in_reader;
}
-
- public void report() {
- if (idle_thread != null)
- idle_thread.report(true);
- }
-
}
--- /dev/null
+/*
+ * 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.text.*;
+import java.io.*;
+
+public class AltosFlightReader {
+ String name;
+
+ int serial;
+
+ void init() { }
+
+ AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; }
+
+ void close(boolean interrupted) { }
+
+ void set_channel(int channel) { }
+
+ void update(AltosState state) throws InterruptedException { }
+}
--- /dev/null
+/*
+ * 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.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class AltosFlightUI extends JFrame {
+ String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
+ Object[][] statusData = { { "0", "pad", "-50", "0" } };
+
+ AltosVoice voice;
+ AltosFlightReader reader;
+ AltosDisplayThread thread;
+
+ private Box vbox;
+ private AltosStatusTable flightStatus;
+ private AltosInfoTable flightInfo;
+
+ public int width() {
+ return flightInfo.width();
+ }
+
+ public int height() {
+ return flightStatus.height() + flightInfo.height();
+ }
+
+ void stop_display() {
+ if (thread != null && thread.isAlive()) {
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException ie) {}
+ }
+ thread = null;
+ }
+
+ void disconnect() {
+ stop_display();
+ }
+
+ public AltosFlightUI(AltosVoice in_voice, AltosFlightReader in_reader, final int serial) {
+ voice = in_voice;
+ reader = in_reader;
+
+ java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
+ if (imgURL != null)
+ setIconImage(new ImageIcon(imgURL).getImage());
+
+ setTitle(String.format("AltOS %s", reader.name));
+
+ flightStatus = new AltosStatusTable();
+
+ vbox = new Box (BoxLayout.Y_AXIS);
+ vbox.add(flightStatus);
+
+ flightInfo = new AltosInfoTable();
+ vbox.add(flightInfo.box());
+
+ this.add(vbox);
+
+ if (serial >= 0) {
+ JMenuBar menubar = new JMenuBar();
+
+ // Channel menu
+ {
+ JMenu menu = new AltosChannelMenu(AltosPreferences.channel(serial));
+ menu.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int channel = Integer.parseInt(e.getActionCommand());
+ reader.set_channel(channel);
+ AltosPreferences.set_channel(serial, channel);
+ }
+ });
+ menu.setMnemonic(KeyEvent.VK_C);
+ menubar.add(menu);
+ }
+
+ this.setJMenuBar(menubar);
+ }
+
+ this.setSize(new Dimension (width(), height()));
+ this.validate();
+
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ disconnect();
+ setVisible(false);
+ dispose();
+ }
+ });
+
+ this.setVisible(true);
+
+ thread = new AltosDisplayThread(this, voice, flightStatus, flightInfo, reader);
+
+ thread.start();
+ }
+
+ public AltosFlightUI (AltosVoice in_voice, AltosFlightReader in_reader) {
+ this(in_voice, in_reader, -1);
+ }
+}
FileWriter log_file;
Thread log_thread;
- void close() throws IOException {
- if (log_file != null)
- log_file.close();
+ void close() {
+ if (log_file != null) {
+ try {
+ log_file.close();
+ } catch (IOException io) {
+ }
+ }
+ if (log_thread != null)
+ log_thread.interrupt();
}
boolean open (AltosTelemetry telem) throws IOException {
} catch (InterruptedException ie) {
} catch (IOException ie) {
}
- try {
- close();
- } catch (IOException ie) {
- }
+ close();
}
public AltosLog (AltosSerial s) {
final static String logdirPreference = "LOGDIR";
/* channel preference name */
- final static String channelPreference = "CHANNEL";
+ final static String channelPreferenceFormat = "CHANNEL-%d";
/* voice preference name */
final static String voicePreference = "VOICE";
/* Log directory */
static File logdir;
- /* Telemetry channel */
- static int channel;
+ /* Channel (map serial to channel) */
+ static Hashtable<Integer, Integer> channels;
/* Voice preference */
static boolean voice;
logdir.mkdirs();
}
- channel = preferences.getInt(channelPreference, 0);
+ channels = new Hashtable<Integer,Integer>();
voice = preferences.getBoolean(voicePreference, true);
return logdir;
}
- public static void set_channel(int new_channel) {
- channel = new_channel;
+ public static void set_channel(int serial, int new_channel) {
+ channels.put(serial, new_channel);
synchronized (preferences) {
- preferences.putInt(channelPreference, channel);
+ preferences.putInt(String.format(channelPreferenceFormat, serial), new_channel);
flush_preferences();
}
}
- public static int channel() {
+ public static int channel(int serial) {
+ if (channels.containsKey(serial))
+ return channels.get(serial);
+ int channel = preferences.getInt(String.format(channelPreferenceFormat, serial), 0);
+ channels.put(serial, channel);
return channel;
}
--- /dev/null
+/*
+ * 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.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.table.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.util.prefs.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/*
+ * Open an existing telemetry file and replay it in realtime
+ */
+
+public class AltosReplayReader extends AltosFlightReader {
+ Iterator<AltosRecord> iterator;
+
+ public AltosRecord read() {
+ if (iterator.hasNext())
+ return iterator.next();
+ return null;
+ }
+
+ public void close (boolean interrupted) {
+ }
+
+ void update(AltosState state) throws InterruptedException {
+ /* Make it run in realtime after the rocket leaves the pad */
+ if (state.state > Altos.ao_flight_pad)
+ Thread.sleep((int) (Math.min(state.time_change,10) * 1000));
+ }
+
+ public AltosReplayReader(Iterator<AltosRecord> in_iterator, String in_name) {
+ iterator = in_iterator;
+ name = in_name;
+ }
+}
+++ /dev/null
-/*
- * 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.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import javax.swing.table.*;
-import java.io.*;
-import java.util.*;
-import java.text.*;
-import java.util.prefs.*;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import altosui.Altos;
-import altosui.AltosSerial;
-import altosui.AltosSerialMonitor;
-import altosui.AltosRecord;
-import altosui.AltosTelemetry;
-import altosui.AltosState;
-import altosui.AltosDeviceDialog;
-import altosui.AltosPreferences;
-import altosui.AltosLog;
-import altosui.AltosVoice;
-import altosui.AltosFlightInfoTableModel;
-import altosui.AltosChannelMenu;
-import altosui.AltosFlashUI;
-import altosui.AltosLogfileChooser;
-import altosui.AltosCSVUI;
-import altosui.AltosLine;
-import altosui.AltosStatusTable;
-import altosui.AltosInfoTable;
-import altosui.AltosDisplayThread;
-
-/*
- * Open an existing telemetry file and replay it in realtime
- */
-
-public class AltosReplayThread extends AltosDisplayThread {
- Iterator<AltosRecord> iterator;
- String name;
-
- public AltosRecord read() {
- if (iterator.hasNext())
- return iterator.next();
- return null;
- }
-
- public void close (boolean interrupted) {
- if (!interrupted)
- report();
- }
-
- void update(AltosState state) throws InterruptedException {
- /* Make it run in realtime after the rocket leaves the pad */
- if (state.state > Altos.ao_flight_pad)
- Thread.sleep((int) (Math.min(state.time_change,10) * 1000));
- }
-
- public AltosReplayThread(Frame parent, Iterator<AltosRecord> in_iterator,
- String in_name, AltosVoice voice,
- AltosStatusTable status, AltosInfoTable info) {
- super(parent, voice, status, info);
- iterator = in_iterator;
- name = in_name;
- }
-}
print("~\nE 0\n");
flush_output();
set_monitor(monitor_mode);
+ set_channel(AltosPreferences.channel());
+ set_callsign(AltosPreferences.callsign());
}
public void set_channel(int channel) {
public class AltosStatusTable extends JTable {
private AltosFlightStatusTableModel flightStatusModel;
- JFrame frame;
-
private Font statusFont = new Font("SansSerif", Font.BOLD, 24);
- public AltosStatusTable(JFrame in_frame) {
+ public AltosStatusTable() {
super((TableModel) new AltosFlightStatusTableModel());
flightStatusModel = (AltosFlightStatusTableModel) getModel();
- frame = in_frame;
setFont(statusFont);
--- /dev/null
+/*
+ * 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.text.*;
+import java.io.*;
+import java.util.concurrent.*;
+
+class AltosTelemetryReader extends AltosFlightReader {
+ AltosDevice device;
+ AltosSerial serial;
+ AltosLog log;
+
+ LinkedBlockingQueue<AltosLine> telem;
+
+ AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException {
+ AltosLine l = telem.take();
+ if (l.line == null)
+ throw new IOException("IO error");
+ return new AltosTelemetry(l.line);
+ }
+
+ void close(boolean interrupted) {
+ serial.remove_monitor(telem);
+ log.close();
+ serial.close();
+ }
+
+ void set_channel(int channel) {
+ serial.set_channel(channel);
+ }
+
+ void set_callsign(String callsign) {
+ serial.set_callsign(callsign);
+ }
+
+ public AltosTelemetryReader (AltosDevice in_device) throws FileNotFoundException, IOException {
+ device = in_device;
+ serial = new AltosSerial();
+ log = new AltosLog(serial);
+ name = device.getPath();
+
+ telem = new LinkedBlockingQueue<AltosLine>();
+ serial.add_monitor(telem);
+ }
+}
import altosui.AltosLog;
import altosui.AltosVoice;
import altosui.AltosFlightInfoTableModel;
-import altosui.AltosChannelMenu;
import altosui.AltosFlashUI;
import altosui.AltosLogfileChooser;
import altosui.AltosCSVUI;
import libaltosJNI.*;
public class AltosUI extends JFrame {
- private int channel = -1;
-
- private AltosStatusTable flightStatus;
- private AltosInfoTable flightInfo;
- private AltosSerial serial_line;
- private AltosLog altos_log;
- private Box vbox;
-
public AltosVoice voice = new AltosVoice();
public static boolean load_library(Frame frame) {
return true;
}
+ void telemetry_window(AltosDevice device) {
+ try {
+ AltosFlightReader reader = new AltosTelemetryReader(device);
+ if (reader != null)
+ new AltosFlightUI(voice, reader, device.getSerial());
+ } catch (FileNotFoundException ee) {
+ JOptionPane.showMessageDialog(AltosUI.this,
+ String.format("Cannot open device \"%s\"",
+ device.getPath()),
+ "Cannot open target device",
+ JOptionPane.ERROR_MESSAGE);
+ } catch (IOException ee) {
+ JOptionPane.showMessageDialog(AltosUI.this,
+ device.getPath(),
+ "Unkonwn I/O error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
public AltosUI() {
load_library(null);
- String[] statusNames = { "Height (m)", "State", "RSSI (dBm)", "Speed (m/s)" };
- Object[][] statusData = { { "0", "pad", "-50", "0" } };
-
java.net.URL imgURL = AltosUI.class.getResource("/altus-metrum-16x16.jpg");
if (imgURL != null)
setIconImage(new ImageIcon(imgURL).getImage());
AltosPreferences.init(this);
- vbox = Box.createVerticalBox();
- this.add(vbox);
-
- flightStatus = new AltosStatusTable(this);
-
- vbox.add(flightStatus);
-
- flightInfo = new AltosInfoTable();
- vbox.add(flightInfo.box());
-
setTitle("AltOS");
createMenu();
- serial_line = new AltosSerial();
- altos_log = new AltosLog(serial_line);
int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
- this.setSize(new Dimension (flightInfo.width(),
- flightStatus.height() + flightInfo.height()));
+ this.setSize(new Dimension (300, 100));
this.validate();
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
System.exit(0);
}
});
- voice.speak("Rocket flight monitor ready.");
- }
-
- class DeviceThread extends AltosDisplayThread {
- AltosSerial serial;
- LinkedBlockingQueue<AltosLine> telem;
-
- AltosRecord read() throws InterruptedException, ParseException, AltosCRCException, IOException {
- AltosLine l = telem.take();
- if (l.line == null)
- throw new IOException("IO error");
- return new AltosTelemetry(l.line);
- }
-
- void close(boolean interrupted) {
- serial.close();
- serial.remove_monitor(telem);
- }
-
- public DeviceThread(AltosSerial s, String in_name, AltosVoice voice, AltosStatusTable status, AltosInfoTable info) {
- super(AltosUI.this, voice, status, info);
- serial = s;
- telem = new LinkedBlockingQueue<AltosLine>();
- serial.add_monitor(telem);
- name = in_name;
- }
}
private void ConnectToDevice() {
AltosDevice device = AltosDeviceDialog.show(AltosUI.this,
AltosDevice.product_basestation);
- if (device != null) {
- try {
- stop_display();
- serial_line.open(device);
- DeviceThread thread = new DeviceThread(serial_line, device.getPath(), voice, flightStatus, flightInfo);
- serial_line.set_channel(AltosPreferences.channel());
- serial_line.set_callsign(AltosPreferences.callsign());
- run_display(thread);
- } catch (FileNotFoundException ee) {
- JOptionPane.showMessageDialog(AltosUI.this,
- String.format("Cannot open device \"%s\"",
- device.getPath()),
- "Cannot open target device",
- JOptionPane.ERROR_MESSAGE);
- } catch (IOException ee) {
- JOptionPane.showMessageDialog(AltosUI.this,
- device.getPath(),
- "Unkonwn I/O error",
- JOptionPane.ERROR_MESSAGE);
- }
- }
- }
-
- void DisconnectFromDevice () {
- stop_display();
+ if (device != null)
+ telemetry_window(device);
}
void ConfigureCallsign() {
result = JOptionPane.showInputDialog(AltosUI.this,
"Configure Callsign",
AltosPreferences.callsign());
- if (result != null) {
+ if (result != null)
AltosPreferences.set_callsign(result);
- if (serial_line != null)
- serial_line.set_callsign(result);
- }
}
void ConfigureTeleMetrum() {
new AltosFlashUI(AltosUI.this);
}
-
- Thread display_thread;
-
- private void stop_display() {
- if (display_thread != null && display_thread.isAlive()) {
- display_thread.interrupt();
- try {
- display_thread.join();
- } catch (InterruptedException ie) {}
- }
- display_thread = null;
- }
-
- private void run_display(Thread thread) {
- stop_display();
- display_thread = thread;
- display_thread.start();
- }
-
/*
* Replay a flight from telemetry data
*/
AltosLogfileChooser chooser = new AltosLogfileChooser(
AltosUI.this);
AltosRecordIterable iterable = chooser.runDialog();
- if (iterable != null)
- run_display(new AltosReplayThread(this, iterable.iterator(),
- chooser.filename(),
- voice,
- flightStatus,
- flightInfo));
+ if (iterable != null) {
+ AltosFlightReader reader = new AltosReplayReader(iterable.iterator(),
+ chooser.filename());
+ new AltosFlightUI(voice, reader);
+ }
}
/* Connect to TeleMetrum, either directly or through
});
menu.add(item);
- item = new JMenuItem("Flash Image",KeyEvent.VK_F);
+ item = new JMenuItem("Flash Image",KeyEvent.VK_I);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
FlashImage();
});
menu.add(item);
- item = new JMenuItem("Export Data",KeyEvent.VK_F);
+ item = new JMenuItem("Export Data",KeyEvent.VK_E);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ExportData();
});
menu.add(item);
- item = new JMenuItem("Graph Data",KeyEvent.VK_F);
+ item = new JMenuItem("Graph Data",KeyEvent.VK_G);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
GraphData();
ActionEvent.CTRL_MASK));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
+ System.out.printf("exiting\n");
System.exit(0);
}
});
}
// Device menu
- {
+ if (false) {
menu = new JMenu("Device");
menu.setMnemonic(KeyEvent.VK_D);
menubar.add(menu);
});
menu.add(item);
- item = new JMenuItem("Disconnect from Device",KeyEvent.VK_D);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- DisconnectFromDevice();
- }
- });
- menu.add(item);
-
menu.addSeparator();
item = new JMenuItem("Set Callsign",KeyEvent.VK_S);
});
menu.add(item);
}
-
- // Channel menu
- {
- menu = new AltosChannelMenu(AltosPreferences.channel());
- menu.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- int new_channel = Integer.parseInt(e.getActionCommand());
- AltosPreferences.set_channel(new_channel);
- serial_line.set_channel(new_channel);
- }
- });
- menu.setMnemonic(KeyEvent.VK_C);
- menubar.add(menu);
- }
-
this.setJMenuBar(menubar);
-
}
static AltosRecordIterable open_logfile(String filename) {
} else {
AltosUI altosui = new AltosUI();
altosui.setVisible(true);
+
+ AltosDevice[] devices = AltosDevice.list(AltosDevice.product_basestation);
+ for (int i = 0; i < devices.length; i++)
+ altosui.telemetry_window(devices[i]);
}
}
}
AltosFlash.java \
AltosFlashUI.java \
AltosFlightInfoTableModel.java \
+ AltosFlightReader.java \
AltosFlightStatusTableModel.java \
+ AltosFlightUI.java \
AltosGPS.java \
AltosGreatCircle.java \
AltosHexfile.java \
AltosReader.java \
AltosRecord.java \
AltosRecordIterable.java \
- AltosReplayThread.java \
+ AltosTelemetryReader.java \
+ AltosReplayReader.java \
AltosRomconfig.java \
AltosRomconfigUI.java \
AltosSerial.java \