<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="extra-src"/>
+ <classpathentry kind="src" path="src-extra"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/lib/miglayout15-swing.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JCommon 1.0.16"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JFreeChart 1.0.13"/>
- <classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/extra-lib/RXTXcomm.jar"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-core-1.1.jar"/>
+ <classpathentry kind="lib" path="lib-test/hamcrest-library-1.1.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-2.5.1.jar"/>
+ <classpathentry kind="lib" path="lib-test/jmock-junit4-2.5.1.jar"/>
+ <classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
+ <classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+2009-10-04 Sampo Niskanen
+
+ * [BUG] Fixed too high configuration dialogs
+
+2009-10-03 Sampo Niskanen
+
+ * Added debug information to ant build file compilation
+ * Implemented update information fetching (client side)
+
2009-09-26 Sampo Niskanen
* Implemented custom material editing
Must-have:
-- Allow editing user-defined materials
- Go through thrust curves and correct errors
- Add styrofoam and depron materials
Bugs:
- Simulation plot dialog forces dialog one button row too high (All/None)
-- All configuration dialogs too high
+- Unit tests fail from ant script
Maybe:
- Windows executable wrapper (launch4j)
+- Inform user about software updates
Postponed:
- Simulate other branches
- Implement setDefaults() method for RocketComponent
- BUG: Inner tube cluster rotation, edit with spinner arrows, slider wrong
-- Inform user about software updates
- Reading thrust curves from external directory
- NAR/CNES/etc competition validity checking
+- Running from command line
+- Print support
+- Saving as SVG
Refactoring tasks:
+- Move startup class to src14 directory, remove reflection
- Remove database etc. initialization from class initialization,
create separate set of test motors
- Extract event rules and data saving from Simulator into listeners
- Change SimulationStatus to include methods for obtaining basic
- position (maybe even an interface)
+ position (maybe even change to an interface, implements Cloneable)
- Change Motor (immutable) to be a factory of MotorInstance (stateful)
- Save file as oldest OpenRocket format possible (for 0.9.4)
- Non-exception bug handling
- JTree text is cropped unnecessarily
+- Allow editing user-defined materials
+- [BUG] All configuration dialogs too high
build.source=default
+
+# Whether checking for updates is enabled by default.
+
+build.checkupdates=true
<path id="test-classpath">
<path refid="classpath"/>
+ <pathelement location="${basedir}"/>
<pathelement location="${build-test.dir}"/>
<pathelement location="${classes.dir}"/>
- <pathelement location="${ant.library.dir}/junit4.jar"/>
+<!-- <pathelement location="${ant.library.dir}/junit4.jar"/> -->
+ <pathelement location="lib-test/junit-4.7.jar"/>
</path>
<target name="build">
<mkdir dir="${classes.dir}"/>
<echo>Compiling main classes</echo>
- <javac srcdir="${src.dir}" destdir="${classes.dir}" excludes="${main-dir}/*" classpathref="classpath"/>
+ <javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" excludes="${main-dir}/*" classpathref="classpath"/>
<echo>Compiling startup classes</echo>
- <javac srcdir="${src.dir}/${main-dir}" destdir="${classes.dir}" source="1.4" classpathref="classpath"/>
+ <javac debug="true" srcdir="${src.dir}/${main-dir}" destdir="${classes.dir}" source="1.4" classpathref="classpath"/>
<copy file="build.properties" todir="${dist.dir}"/>
</target>
<target name="unittest" description="Execute unit tests" depends="build">
<echo>Building unit tests</echo>
<mkdir dir="${build-test.dir}"/>
- <javac srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="test-classpath"/>
+ <javac debug="true" srcdir="${src-test.dir}" destdir="${build-test.dir}" classpathref="test-classpath"/>
<echo>Running unit tests</echo>
<mkdir dir="tmp/rawtestoutput"/>
+++ /dev/null
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.TimeZone;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- *
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class Alt15K {
- public static final int TIMEOUT = 500;
- public static final int RWDELAY = 5;
-
- private static final boolean DEBUG = false;
-
- private static final Charset CHARSET = Charset.forName("ISO-8859-1");
-
- private final CommPortIdentifier portID;
- private SerialPort port = null;
- private InputStream is = null;
- private OutputStream os = null;
-
-
-
- @SuppressWarnings("unchecked")
- public static String[] getNames() {
- ArrayList<String> list = new ArrayList<String>();;
-
- Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
- while (pids.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
- if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
- list.add(pid.getName());
- }
- return list.toArray(new String[0]);
- }
-
-
-
- @SuppressWarnings("unchecked")
- public Alt15K(String name) throws IOException {
- CommPortIdentifier pID = null;
-
- Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
- while (portIdentifiers.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-
- if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
- pid.getName().equals(name)) {
- pID = pid;
- break;
- }
- }
-
- if (pID==null) {
- throw new IOException("Port '"+name+"' not found.");
- }
- this.portID = pID;
- }
-
-
- /**
- * Get altimeter flight data. The flight profile is chosen by the parameter n,
- * 0 = latest flight, 1 = second latest, etc.
- *
- * @param n Which flight profile to use (0=newest, 1=second newest, etc)
- * @return The altimeter flight data
- * @throws IOException in case of IOException
- * @throws PortInUseException in case of PortInUseException
- */
- public AltData getData(int n) throws IOException, PortInUseException {
- AltData alt = new AltData();
- ArrayList<Integer> data = new ArrayList<Integer>();
- byte[] buf;
- byte[] buf2 = new byte[0];
- boolean identical = false; // Whether identical lines have been read
-
- if (DEBUG)
- System.out.println(" Retrieving altimeter data n="+n);
-
- try {
- open();
-
- // Get version and position data
- byte[] ver = getVersionData();
- alt.setVersion(new byte[] { ver[0],ver[1] });
-
- // Calculate the position requested
- if (n > 2)
- n = 2;
- int position = ver[2] - n;
- while (position < 0)
- position += 3;
-
- if (DEBUG)
- System.out.println(" Requesting data from position "+position);
-
- // Request the data
- write("D");
- write((byte)position);
- write("PS");
-
- sleep();
-
- // Read preliminary data
- buf = read(4);
- int msl_level = combine(buf[0],buf[1]);
- int datacount = combine(buf[2],buf[3]);
-
- if (DEBUG)
- System.out.println(" Preliminary data msl="+msl_level+" count="+datacount);
-
- alt.setMslLevel(msl_level-6000);
- alt.setDataSamples(datacount);
-
- if (DEBUG)
- System.out.println(" Retrieving "+datacount+" samples");
-
- long t = System.currentTimeMillis();
-
- int count = 0;
- while (count < datacount) {
- sleep();
- write("G");
- sleep();
- buf = read(17);
-
- if (buf.length == 17) {
- // Checksum = sum of all bytes + 1
- // (signedness does not change the result)
- byte checksum = 1;
- for (int i=0; i<16; i++)
- checksum += buf[i];
- if (checksum != buf[16]) {
- printBytes("ERROR: Checksum fail on data (computed="+checksum+
- " orig="+buf[16]+")",buf);
- System.out.println("Ignoring error");
- }
- } else {
- System.err.println("ERROR: Only "+buf.length+" bytes read, should be 17");
- }
-
- for (int i=0; i<buf.length-1; i+=2) {
- data.add(combine(buf[i],buf[i+1]));
- count++;
- }
-
- /*
- * Check whether the data is identical to the previous data batch. If reading
- * too fast, the data seems to become duplicated in the transfer. We need to check
- * whether this has happened by attempting to read more data than is normally
- * available.
- */
- int c, l=Math.min(buf.length, buf2.length);
- for (c=0; c<l; c++) {
- if (buf[c] != buf2[c])
- break;
- }
- if (c==l && buf.length == buf2.length)
- identical = true;
- buf2 = buf.clone();
- }
-
- if (DEBUG)
- System.out.println(" Retrieved "+data.size()+" samples in "+
- (System.currentTimeMillis()-t)+" ms");
-
-
- // In case of identical lines, check for more data. This would mean that the
- // transfer was corrupted.
- if (identical) {
- System.err.println("WARNING: Duplicate data detected, possible error");
- }
-
- // Test for more data
- if (DEBUG)
- System.out.println(" Testing for more data");
- sleep();
- write("G");
- sleep();
- buf = read(17);
- if (buf.length > 0) {
- System.err.println("ERROR: Data available after transfer! (length="+buf.length+")");
- }
-
-
-
-
-
-
- // Create an int[] array and set it
- int[] d = new int[data.size()];
- for (int i=0; i<d.length; i++)
- d[i] = data.get(i);
- alt.setData(d);
-
- // Catch all exceptions, close the port and re-throw the exception
- } catch (PortInUseException e) {
- close();
- throw e;
- } catch (IOException e) {
- close();
- throw e;
- } catch (UnsupportedCommOperationException e) {
- close();
- throw new RuntimeException("Required function of RxTx library not supported",e);
- } catch (RuntimeException e) {
- // Catch-all for all other types of exceptions
- close();
- throw e;
- }
-
- close();
- return alt;
- }
-
-
-
-
- private byte[] getVersionData() throws PortInUseException, IOException,
- UnsupportedCommOperationException {
- byte[] ver = new byte[3];
- byte[] buf;
-
- if (DEBUG)
- System.out.println(" Retrieving altimeter version information");
-
- // Signal to altimeter we are here
- write((byte)0);
- sleep(15); // Sleep for 15ms, data is incoming at 10 samples/sec
-
- // Get altimeter version, skip zeros
- write("PV");
- sleep();
- buf = readSkipZero(2);
- sleep();
- if (buf.length != 2) {
- close();
- throw new IOException("Communication with altimeter failed.");
- }
- ver[0] = buf[0];
- ver[1] = buf[1];
-
- // Get position of newest data
- write("M");
- sleep();
- buf = read(1);
- if (buf.length != 1) {
- close();
- throw new IOException("Communication with altimeter failed.");
- }
- ver[2] = buf[0];
-
- if (DEBUG)
- System.out.println(" Received version info "+ver[0]+"."+ver[1]+", position "+ver[2]);
-
- return ver;
- }
-
-
- /**
- * Delay the communication by a small delay (RWDELAY ms).
- */
- private void sleep() {
- sleep(RWDELAY);
- }
-
- /**
- * Sleep for the given amount of milliseconds.
- */
- private void sleep(int n) {
- try {
- Thread.sleep(n);
- } catch (InterruptedException ignore) { }
- }
-
-
- private void open()
- throws PortInUseException, IOException, UnsupportedCommOperationException {
- if (port != null) {
- System.err.println("ERROR: open() called with port="+port);
- Thread.dumpStack();
- close();
- }
-
- if (DEBUG) {
- System.out.println(" Opening port...");
- }
-
- port = (SerialPort)portID.open("OpenRocket",1000);
-
- port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
- SerialPort.PARITY_NONE);
-
- port.setInputBufferSize(1);
- port.setOutputBufferSize(1);
-
- port.enableReceiveTimeout(TIMEOUT);
-
- is = port.getInputStream();
- os = port.getOutputStream();
- }
-
-
- private byte[] readSkipZero(int n) throws IOException, UnsupportedCommOperationException {
- long t = System.currentTimeMillis() + TIMEOUT*2;
-
- if (DEBUG) {
- System.out.println(" readSkipZero "+n+" bytes");
- }
-
- while (System.currentTimeMillis() < t) {
- byte[] buf = read(n);
- if (DEBUG)
- printBytes(" Received",buf);
-
- if (buf.length == 0) // No data available
- return buf;
-
- // Skip zeros
- int i;
- for (i=0; i<buf.length; i++)
- if (buf[i] != 0)
- break;
-
- if (i==0) // No zeros to skip
- return buf;
-
- if (i < buf.length) {
- // Partially read
- int count = buf.length-i; // No. of data bytes
- byte[] array = new byte[n];
- System.arraycopy(buf, i, array, 0, count);
- buf = read(n-count);
- if (DEBUG)
- printBytes(" Received (partial)",buf);
- System.arraycopy(buf, 0, array, count, buf.length);
-
- if (DEBUG)
- printBytes(" Returning",array);
- return array;
- }
- }
-
- if (DEBUG)
- System.out.println(" No data read, returning empty");
- return new byte[0]; // no data, only zeros
- }
-
-
- private byte[] read(int n) throws IOException, UnsupportedCommOperationException {
- byte[] bytes = new byte[n];
-
- port.enableReceiveThreshold(n);
-
- long t = System.currentTimeMillis() + TIMEOUT;
- int count = 0;
-
- if (DEBUG)
- System.out.println(" Reading "+n+" bytes");
-
- while (count < n && System.currentTimeMillis() < t) {
- byte[] buf = new byte[n-count];
- int c = is.read(buf);
- System.arraycopy(buf, 0, bytes, count, c);
- count += c;
- }
-
- byte[] array = new byte[count];
- System.arraycopy(bytes, 0, array, 0, count);
-
- if (DEBUG)
- printBytes(" Returning",array);
-
- return array;
- }
-
- private void write(String s) throws IOException {
- write(s.getBytes(CHARSET));
- }
-
- private void write(byte ... bytes) throws IOException {
- if (DEBUG)
- printBytes(" Writing",bytes);
- os.write(bytes);
- }
-
- private void close() {
- if (DEBUG)
- System.out.println(" Closing port");
-
- SerialPort p = port;
- port = null;
- is = null;
- os = null;
- if (p != null)
- p.close();
- }
-
-
-
-
-
- public static void main(String[] arg) {
-
- if (arg.length != 1) {
- System.err.println("Usage: java Alt15K <basename>");
- System.err.println("Files will be saved <basename>-old.log, -med and -new");
- return;
- }
-
-
- String device = null;
- String[] devices = Alt15K.getNames();
- for (int i=0; i<devices.length; i++) {
- if (devices[i].matches(".*USB.*")) {
- device = devices[i];
- break;
- }
- }
- if (device == null) {
- System.out.println("Device not found.");
- return;
- }
-
-
- System.out.println("Selected device "+device);
-
- AltData alt = null;
- String file;
- try {
- Alt15K p = new Alt15K(device);
-
- System.out.println("Retrieving newest data...");
- alt = p.getData(0);
- System.out.println("Apogee at "+alt.getApogee()+" feet");
-
- file = arg[0]+"-new.log";
- System.out.println("Saving data to "+file+"...");
- savefile(file,alt);
-
-
- System.out.println("Retrieving medium data...");
- alt = p.getData(1);
- System.out.println("Apogee at "+alt.getApogee()+" feet");
-
- file = arg[0]+"-med.log";
- System.out.println("Saving data to "+file+"...");
- savefile(file,alt);
-
-
- System.out.println("Retrieving oldest data...");
- alt = p.getData(2);
- System.out.println("Apogee at "+alt.getApogee()+" feet");
-
- file = arg[0]+"-old.log";
- System.out.println("Saving data to "+file+"...");
- savefile(file,alt);
-
- } catch (IOException e) {
- e.printStackTrace();
- } catch (PortInUseException e) {
- e.printStackTrace();
- }
-
-// System.out.println(alt);
-// alt.printData();
-
- }
-
-
- static private void savefile(String file, AltData data) throws FileNotFoundException {
-
- PrintStream output = new PrintStream(file);
-
- // WTF is this so difficult?!?
- DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
- TimeZone tz=TimeZone.getTimeZone("GMT+3");
- fmt.setTimeZone(tz);
-
- output.println("# Alt15K data, file "+file);
- output.println("# Data retrieved at: "+fmt.format(new Date()));
- output.println("# Values are in feet above launch level");
- output.println("# ");
- output.println("# Apogee = "+data.getApogee());
- output.println("# MSL level = "+data.getMslLevel());
- output.println("# Data count = "+data.getDataSamples());
-
- byte[] b = data.getVersion();
- String s="";
- for (int i=0; i<b.length; i++) {
- if (s.equals(""))
- s = ""+((int)b[i]);
- else
- s = s+"."+((int)b[i]);
- }
- output.println("# Altimeter version = " + s);
-
- int[] values = data.getData();
- for (int i=0; i < values.length; i++) {
- output.println(""+values[i]);
- }
-
- output.close();
- }
-
-
- static private void printBytes(String str, byte[] b) {
- printBytes(str, b,b.length);
- }
-
- static private void printBytes(String str, byte[] b, int n) {
- String s;
- s = str+" "+n+" bytes:";
- for (int i=0; i<n; i++) {
- s += " "+unsign(b[i]);
- }
- System.out.println(s);
- }
-
- static private int unsign(byte b) {
- if (b >= 0)
- return b;
- else
- return 256 + b;
- }
-
- @SuppressWarnings("unused")
- static private int combine(int a, int b) {
- return 256*a + b;
- }
-
- static private int combine(byte a, byte b) {
- int val = 256*unsign(a)+unsign(b);
- if (val <= 32767)
- return val;
- else
- return val-65536;
-
- }
-
-}
+++ /dev/null
-package altimeter;
-
-public class AltData {
-
- private int mslLevel = 0;
- private int samples = 0;
- private int[] data = null;
- private byte[] version = null;
-
-
- public void setMslLevel(int msl) {
- mslLevel = msl;
- }
- public int getMslLevel() {
- return mslLevel;
- }
-
- public void setDataSamples(int s) {
- samples = s;
- }
- public int getDataSamples() {
- return samples;
- }
-
- public void setVersion(byte[] v) {
- if (v==null)
- version = null;
- else
- version = v.clone();
- }
- public byte[] getVersion() {
- if (version == null)
- return null;
- return version.clone();
- }
-
- public void setData(int[] data) {
- if (data==null)
- this.data = null;
- else
- this.data = data.clone();
- }
- public int[] getData() {
- if (data == null)
- return null;
- return data.clone();
- }
-
- public int getApogee() {
- if (data == null || data.length==0)
- return 0;
- int max = Integer.MIN_VALUE;
- for (int i=0; i<data.length; i++) {
- if (data[i] > max)
- max = data[i];
- }
- return max;
- }
-
- @Override
- public String toString() {
- String s = "AltData(";
- s += "MSL:"+getMslLevel()+",";
- s += "Apogee:"+getApogee()+",";
- s += "Samples:"+getDataSamples();
- s += ")";
- return s;
- }
-
- public void printData() {
- System.out.println(toString()+":");
- for (int i=0; i<data.length; i+=8) {
- String s = " "+i+":";
- for (int j=0; j<8 && (i+j)<data.length; j++) {
- s += " "+data[i+j];
- }
- System.out.println(s);
- }
- }
-
-}
+++ /dev/null
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- *
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class RotationLogger {
- private static final boolean DEBUG = false;
-
- private static final int BYTES = 65536;
-
-
- private final CommPortIdentifier portID;
- private SerialPort port = null;
- private InputStream is = null;
- private OutputStream os = null;
-
-
-
- @SuppressWarnings("unchecked")
- public static String[] getNames() {
- ArrayList<String> list = new ArrayList<String>();;
-
- Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
- while (pids.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
- if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
- list.add(pid.getName());
- }
- return list.toArray(new String[0]);
- }
-
-
-
-
-
- @SuppressWarnings("unchecked")
- public RotationLogger(String name) throws IOException {
- CommPortIdentifier portID = null;
-
- Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
- while (portIdentifiers.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-
- if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
- pid.getName().equals(name)) {
- portID = pid;
- break;
- }
- }
-
- if (portID==null) {
- throw new IOException("Port '"+name+"' not found.");
- }
- this.portID = portID;
- }
-
-
-
-
-
-
- public void readData() throws IOException, PortInUseException {
- int c;
-
- int[] data = new int[BYTES];
-
- FileOutputStream rawdump = null;
-
-
- try {
- open();
-
- System.err.println("Sending dump mode command...");
-
- for (int i=0; i<16; i++) {
- os.write('D');
- try {
- Thread.sleep(10);
- } catch (InterruptedException ignore) { }
- }
-
- System.err.println("Waiting for response...");
- while (true) {
- c = is.read();
- if (c == 'K') {
- break;
- } else {
- System.err.printf("Received spurious c=%d\n",c);
- }
- }
-
- System.err.println("Received response.");
-
-
-
- System.err.println("Opening 'rawdump'...");
- rawdump = new FileOutputStream("rawdump");
-
-
-
- System.err.println("Performing dump...");
-
- os.write('A');
-
- byte[] buffer = new byte[1024];
- int printCount = 0;
- for (int count=0; count < BYTES; ) {
- if ((BYTES-count) < buffer.length) {
- buffer = new byte[BYTES-count];
- }
-
- int n = is.read(buffer);
- if (n < 0) {
- System.err.println("Error condition, n="+n);
- return;
- }
-
- rawdump.write(buffer, 0, n);
-
- for (int i=0; i<n; i++) {
- data[count+i] = unsign(buffer[i]);
- }
- count += n;
- if (count - printCount > 1024) {
- System.err.println("Read "+count+" bytes...");
- printCount = count;
- }
- }
-
-
- System.err.println("Verifying checksum...");
- int reported = is.read();
-
- byte computed = 0;
- for (int i=0; i < data.length; i++) {
- computed += data[i];
- }
- if (computed == reported) {
- System.err.println("Checksum ok ("+computed+")");
- } else {
- System.err.println("Error in checksum, computed="+computed+
- " reported="+reported);
- }
-
- System.err.println("Communication done.");
-
- } catch (UnsupportedCommOperationException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } finally {
- close();
- if (rawdump != null)
- rawdump.close();
- }
-
- convertData(data);
-
- }
-
-
-
- //////////// Data interpretation //////////////
-
-
- private static void convertData(int[] data) {
-
- System.err.println("Converting data...");
-
- int lastBuffer = data[0xffff];
- if (lastBuffer < 0 || lastBuffer > 3) {
- System.err.println("Illegal last accessed buffer: "+lastBuffer);
- return;
- }
- System.err.println("Last used buffer: "+lastBuffer);
-
- for (int i=4; i>0; i--) {
- int n = (lastBuffer + i) % 4;
- int bufNumber = 4-i;
-
- convertBuffer(data, n * (BYTES/4), bufNumber);
- }
-
- }
-
-
- private static void convertBuffer(int[] data, int position, int bufNumber) {
- int startPosition;
-
- startPosition = data[position + 0xfd] << 8 + data[position+0xfe];
-
- // 50 samples per 128 bytes
- int startTime = (startPosition -position) * 50 / 128;
-
- System.err.println(" Buffer "+ bufNumber + " (at position "+position+")...");
- System.err.println(" Start position "+startPosition+" time "+startTime);
-
- System.out.println("# Buffer "+bufNumber);
- System.out.println("# Start position t="+startTime);
-
-
- int t = 0;
- for (int page = 0; page < 128; page++) {
- int pageStart = position + page * 128;
-
- if (pageStart == startPosition) {
- System.out.println("# ---clip---");
- }
-
- for (int i=0; i<125; i += 5) {
- int sample1, sample2;
-
- int start = pageStart + i;
-// System.err.println("page="+page+" i="+i+
-// " position="+position+" pageStart="+pageStart+" start="+start);
-
- sample1 = (data[start] << 2) + (data[start+1] >> 6);
- sample2 = ((data[start+1] & 0x3f) << 4) + (data[start+2] >> 4);
- System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2);
- t++;
-
- sample1 = ((data[start+2] & 0x0f) << 6) + (data[start+3] >> 2);
- sample2 = ((data[start+3] & 3) << 8) + data[start+4];
- System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2);
- t++;
- }
- }
- }
-
-
-
- private void open() throws PortInUseException, IOException,
- UnsupportedCommOperationException {
-
- if (port != null) {
- System.err.println("ERROR: open() called with port="+port);
- Thread.dumpStack();
- close();
- }
-
- if (DEBUG) {
- System.err.println(" Opening port...");
- }
-
- port = (SerialPort)portID.open("OpenRocket",1000);
-
- port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
- SerialPort.PARITY_NONE);
-
- port.setInputBufferSize(1);
- port.setOutputBufferSize(1);
-
- is = port.getInputStream();
- os = port.getOutputStream();
- }
-
-
- private void close() {
- if (DEBUG)
- System.err.println(" Closing port");
-
- SerialPort p = port;
- port = null;
- is = null;
- if (p != null)
- p.close();
- }
-
-
-
- private static int unsign(byte b) {
- if (b >= 0)
- return b;
- else
- return 256 + b;
- }
-
-
-
-
- public static void main(String[] arg) throws Exception {
-
- if (arg.length > 2) {
- System.err.println("Illegal arguments.");
- return;
- }
- if (arg.length == 1) {
- FileInputStream is = new FileInputStream(arg[0]);
- byte[] buffer = new byte[BYTES];
- int n = is.read(buffer);
- if (n != BYTES) {
- System.err.println("Could read only "+n+" bytes");
- return;
- }
-
- int[] data = new int[BYTES];
- for (int i=0; i<BYTES; i++) {
- data[i] = unsign(buffer[i]);
- }
-
- int checksum=0;
- for (int i=0; i<BYTES; i++) {
- checksum += data[i];
- }
- checksum = checksum%256;
- System.err.println("Checksum: "+checksum);
-
- convertData(data);
- return;
- }
-
-
- String device = null;
- String[] devices = RotationLogger.getNames();
- for (int i=0; i<devices.length; i++) {
- if (devices[i].matches(".*USB.*")) {
- device = devices[i];
- break;
- }
- }
- if (device == null) {
- System.err.println("Device not found.");
- return;
- }
-
-
- System.err.println("Selected device "+device);
-
-
- RotationLogger p = new RotationLogger(device);
-
- p.readData();
-
- }
-
-
-}
+++ /dev/null
-package altimeter;
-
-import gnu.io.CommPortIdentifier;
-import gnu.io.PortInUseException;
-import gnu.io.SerialPort;
-import gnu.io.UnsupportedCommOperationException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Enumeration;
-
-/**
- * Class to interface the PerfectFlite Alt15K/WD altimeter.
- *
- * Also includes a main method that retrieves all flight profiles and saves them to files.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class SerialDownload {
- private static final boolean DEBUG = false;
-
- private static final int MAGIC = 666;
-
- private final CommPortIdentifier portID;
- private SerialPort port = null;
- private InputStream is = null;
-
-
-
- @SuppressWarnings("unchecked")
- public static String[] getNames() {
- ArrayList<String> list = new ArrayList<String>();;
-
- Enumeration pids = CommPortIdentifier.getPortIdentifiers();
-
- while (pids.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
-
- if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
- list.add(pid.getName());
- }
- return list.toArray(new String[0]);
- }
-
-
-
-
-
- @SuppressWarnings("unchecked")
- public SerialDownload(String name) throws IOException {
- CommPortIdentifier portID = null;
-
- Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
- while (portIdentifiers.hasMoreElements()) {
- CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
-
- if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
- pid.getName().equals(name)) {
- portID = pid;
- break;
- }
- }
-
- if (portID==null) {
- throw new IOException("Port '"+name+"' not found.");
- }
- this.portID = portID;
- }
-
-
-
-
-
-
- public void readData() throws IOException, PortInUseException {
- long t0 = -1;
- long t;
-
- int previous = MAGIC;
-
-
- try {
- open();
-
- System.err.println("Ready to read...");
- while (true) {
- int c = is.read();
- t = System.nanoTime();
- if (t0 < 0)
- t0 = t;
-
- System.out.printf("%10.6f %d\n", ((double)t-t0)/1000000000.0, c);
-
- if (previous == MAGIC) {
- previous = c;
- } else {
- System.out.printf("# Altitude: %5d\n", previous*256 + c);
- previous = MAGIC;
- }
-
- if (c < 0)
- break;
- }
-
-
- } catch (UnsupportedCommOperationException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } finally {
- close();
- }
- }
-
-
-
- private void open() throws PortInUseException, IOException,
- UnsupportedCommOperationException {
-
- if (port != null) {
- System.err.println("ERROR: open() called with port="+port);
- Thread.dumpStack();
- close();
- }
-
- if (DEBUG) {
- System.err.println(" Opening port...");
- }
-
- port = (SerialPort)portID.open("OpenRocket",1000);
-
- port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
- SerialPort.PARITY_NONE);
-
- port.setInputBufferSize(1);
- port.setOutputBufferSize(1);
-
- is = port.getInputStream();
- }
-
-
- private void close() {
- if (DEBUG)
- System.err.println(" Closing port");
-
- SerialPort p = port;
- port = null;
- is = null;
- if (p != null)
- p.close();
- }
-
-
-
-
-
- public static void main(String[] arg) throws Exception {
-
- String device = null;
- String[] devices = SerialDownload.getNames();
- for (int i=0; i<devices.length; i++) {
- if (devices[i].matches(".*USB.*")) {
- device = devices[i];
- break;
- }
- }
- if (device == null) {
- System.err.println("Device not found.");
- return;
- }
-
-
- System.err.println("Selected device "+device);
-
-
- SerialDownload p = new SerialDownload(device);
-
- p.readData();
-
- }
-
-
-}
--- /dev/null
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.TimeZone;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ *
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class Alt15K {
+ public static final int TIMEOUT = 500;
+ public static final int RWDELAY = 5;
+
+ private static final boolean DEBUG = false;
+
+ private static final Charset CHARSET = Charset.forName("ISO-8859-1");
+
+ private final CommPortIdentifier portID;
+ private SerialPort port = null;
+ private InputStream is = null;
+ private OutputStream os = null;
+
+
+
+ @SuppressWarnings("unchecked")
+ public static String[] getNames() {
+ ArrayList<String> list = new ArrayList<String>();;
+
+ Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+ while (pids.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+ if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+ list.add(pid.getName());
+ }
+ return list.toArray(new String[0]);
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ public Alt15K(String name) throws IOException {
+ CommPortIdentifier pID = null;
+
+ Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+ while (portIdentifiers.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+
+ if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+ pid.getName().equals(name)) {
+ pID = pid;
+ break;
+ }
+ }
+
+ if (pID==null) {
+ throw new IOException("Port '"+name+"' not found.");
+ }
+ this.portID = pID;
+ }
+
+
+ /**
+ * Get altimeter flight data. The flight profile is chosen by the parameter n,
+ * 0 = latest flight, 1 = second latest, etc.
+ *
+ * @param n Which flight profile to use (0=newest, 1=second newest, etc)
+ * @return The altimeter flight data
+ * @throws IOException in case of IOException
+ * @throws PortInUseException in case of PortInUseException
+ */
+ public AltData getData(int n) throws IOException, PortInUseException {
+ AltData alt = new AltData();
+ ArrayList<Integer> data = new ArrayList<Integer>();
+ byte[] buf;
+ byte[] buf2 = new byte[0];
+ boolean identical = false; // Whether identical lines have been read
+
+ if (DEBUG)
+ System.out.println(" Retrieving altimeter data n="+n);
+
+ try {
+ open();
+
+ // Get version and position data
+ byte[] ver = getVersionData();
+ alt.setVersion(new byte[] { ver[0],ver[1] });
+
+ // Calculate the position requested
+ if (n > 2)
+ n = 2;
+ int position = ver[2] - n;
+ while (position < 0)
+ position += 3;
+
+ if (DEBUG)
+ System.out.println(" Requesting data from position "+position);
+
+ // Request the data
+ write("D");
+ write((byte)position);
+ write("PS");
+
+ sleep();
+
+ // Read preliminary data
+ buf = read(4);
+ int msl_level = combine(buf[0],buf[1]);
+ int datacount = combine(buf[2],buf[3]);
+
+ if (DEBUG)
+ System.out.println(" Preliminary data msl="+msl_level+" count="+datacount);
+
+ alt.setMslLevel(msl_level-6000);
+ alt.setDataSamples(datacount);
+
+ if (DEBUG)
+ System.out.println(" Retrieving "+datacount+" samples");
+
+ long t = System.currentTimeMillis();
+
+ int count = 0;
+ while (count < datacount) {
+ sleep();
+ write("G");
+ sleep();
+ buf = read(17);
+
+ if (buf.length == 17) {
+ // Checksum = sum of all bytes + 1
+ // (signedness does not change the result)
+ byte checksum = 1;
+ for (int i=0; i<16; i++)
+ checksum += buf[i];
+ if (checksum != buf[16]) {
+ printBytes("ERROR: Checksum fail on data (computed="+checksum+
+ " orig="+buf[16]+")",buf);
+ System.out.println("Ignoring error");
+ }
+ } else {
+ System.err.println("ERROR: Only "+buf.length+" bytes read, should be 17");
+ }
+
+ for (int i=0; i<buf.length-1; i+=2) {
+ data.add(combine(buf[i],buf[i+1]));
+ count++;
+ }
+
+ /*
+ * Check whether the data is identical to the previous data batch. If reading
+ * too fast, the data seems to become duplicated in the transfer. We need to check
+ * whether this has happened by attempting to read more data than is normally
+ * available.
+ */
+ int c, l=Math.min(buf.length, buf2.length);
+ for (c=0; c<l; c++) {
+ if (buf[c] != buf2[c])
+ break;
+ }
+ if (c==l && buf.length == buf2.length)
+ identical = true;
+ buf2 = buf.clone();
+ }
+
+ if (DEBUG)
+ System.out.println(" Retrieved "+data.size()+" samples in "+
+ (System.currentTimeMillis()-t)+" ms");
+
+
+ // In case of identical lines, check for more data. This would mean that the
+ // transfer was corrupted.
+ if (identical) {
+ System.err.println("WARNING: Duplicate data detected, possible error");
+ }
+
+ // Test for more data
+ if (DEBUG)
+ System.out.println(" Testing for more data");
+ sleep();
+ write("G");
+ sleep();
+ buf = read(17);
+ if (buf.length > 0) {
+ System.err.println("ERROR: Data available after transfer! (length="+buf.length+")");
+ }
+
+
+
+
+
+
+ // Create an int[] array and set it
+ int[] d = new int[data.size()];
+ for (int i=0; i<d.length; i++)
+ d[i] = data.get(i);
+ alt.setData(d);
+
+ // Catch all exceptions, close the port and re-throw the exception
+ } catch (PortInUseException e) {
+ close();
+ throw e;
+ } catch (IOException e) {
+ close();
+ throw e;
+ } catch (UnsupportedCommOperationException e) {
+ close();
+ throw new RuntimeException("Required function of RxTx library not supported",e);
+ } catch (RuntimeException e) {
+ // Catch-all for all other types of exceptions
+ close();
+ throw e;
+ }
+
+ close();
+ return alt;
+ }
+
+
+
+
+ private byte[] getVersionData() throws PortInUseException, IOException,
+ UnsupportedCommOperationException {
+ byte[] ver = new byte[3];
+ byte[] buf;
+
+ if (DEBUG)
+ System.out.println(" Retrieving altimeter version information");
+
+ // Signal to altimeter we are here
+ write((byte)0);
+ sleep(15); // Sleep for 15ms, data is incoming at 10 samples/sec
+
+ // Get altimeter version, skip zeros
+ write("PV");
+ sleep();
+ buf = readSkipZero(2);
+ sleep();
+ if (buf.length != 2) {
+ close();
+ throw new IOException("Communication with altimeter failed.");
+ }
+ ver[0] = buf[0];
+ ver[1] = buf[1];
+
+ // Get position of newest data
+ write("M");
+ sleep();
+ buf = read(1);
+ if (buf.length != 1) {
+ close();
+ throw new IOException("Communication with altimeter failed.");
+ }
+ ver[2] = buf[0];
+
+ if (DEBUG)
+ System.out.println(" Received version info "+ver[0]+"."+ver[1]+", position "+ver[2]);
+
+ return ver;
+ }
+
+
+ /**
+ * Delay the communication by a small delay (RWDELAY ms).
+ */
+ private void sleep() {
+ sleep(RWDELAY);
+ }
+
+ /**
+ * Sleep for the given amount of milliseconds.
+ */
+ private void sleep(int n) {
+ try {
+ Thread.sleep(n);
+ } catch (InterruptedException ignore) { }
+ }
+
+
+ private void open()
+ throws PortInUseException, IOException, UnsupportedCommOperationException {
+ if (port != null) {
+ System.err.println("ERROR: open() called with port="+port);
+ Thread.dumpStack();
+ close();
+ }
+
+ if (DEBUG) {
+ System.out.println(" Opening port...");
+ }
+
+ port = (SerialPort)portID.open("OpenRocket",1000);
+
+ port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
+ SerialPort.PARITY_NONE);
+
+ port.setInputBufferSize(1);
+ port.setOutputBufferSize(1);
+
+ port.enableReceiveTimeout(TIMEOUT);
+
+ is = port.getInputStream();
+ os = port.getOutputStream();
+ }
+
+
+ private byte[] readSkipZero(int n) throws IOException, UnsupportedCommOperationException {
+ long t = System.currentTimeMillis() + TIMEOUT*2;
+
+ if (DEBUG) {
+ System.out.println(" readSkipZero "+n+" bytes");
+ }
+
+ while (System.currentTimeMillis() < t) {
+ byte[] buf = read(n);
+ if (DEBUG)
+ printBytes(" Received",buf);
+
+ if (buf.length == 0) // No data available
+ return buf;
+
+ // Skip zeros
+ int i;
+ for (i=0; i<buf.length; i++)
+ if (buf[i] != 0)
+ break;
+
+ if (i==0) // No zeros to skip
+ return buf;
+
+ if (i < buf.length) {
+ // Partially read
+ int count = buf.length-i; // No. of data bytes
+ byte[] array = new byte[n];
+ System.arraycopy(buf, i, array, 0, count);
+ buf = read(n-count);
+ if (DEBUG)
+ printBytes(" Received (partial)",buf);
+ System.arraycopy(buf, 0, array, count, buf.length);
+
+ if (DEBUG)
+ printBytes(" Returning",array);
+ return array;
+ }
+ }
+
+ if (DEBUG)
+ System.out.println(" No data read, returning empty");
+ return new byte[0]; // no data, only zeros
+ }
+
+
+ private byte[] read(int n) throws IOException, UnsupportedCommOperationException {
+ byte[] bytes = new byte[n];
+
+ port.enableReceiveThreshold(n);
+
+ long t = System.currentTimeMillis() + TIMEOUT;
+ int count = 0;
+
+ if (DEBUG)
+ System.out.println(" Reading "+n+" bytes");
+
+ while (count < n && System.currentTimeMillis() < t) {
+ byte[] buf = new byte[n-count];
+ int c = is.read(buf);
+ System.arraycopy(buf, 0, bytes, count, c);
+ count += c;
+ }
+
+ byte[] array = new byte[count];
+ System.arraycopy(bytes, 0, array, 0, count);
+
+ if (DEBUG)
+ printBytes(" Returning",array);
+
+ return array;
+ }
+
+ private void write(String s) throws IOException {
+ write(s.getBytes(CHARSET));
+ }
+
+ private void write(byte ... bytes) throws IOException {
+ if (DEBUG)
+ printBytes(" Writing",bytes);
+ os.write(bytes);
+ }
+
+ private void close() {
+ if (DEBUG)
+ System.out.println(" Closing port");
+
+ SerialPort p = port;
+ port = null;
+ is = null;
+ os = null;
+ if (p != null)
+ p.close();
+ }
+
+
+
+
+
+ public static void main(String[] arg) {
+
+ if (arg.length != 1) {
+ System.err.println("Usage: java Alt15K <basename>");
+ System.err.println("Files will be saved <basename>-old.log, -med and -new");
+ return;
+ }
+
+
+ String device = null;
+ String[] devices = Alt15K.getNames();
+ for (int i=0; i<devices.length; i++) {
+ if (devices[i].matches(".*USB.*")) {
+ device = devices[i];
+ break;
+ }
+ }
+ if (device == null) {
+ System.out.println("Device not found.");
+ return;
+ }
+
+
+ System.out.println("Selected device "+device);
+
+ AltData alt = null;
+ String file;
+ try {
+ Alt15K p = new Alt15K(device);
+
+ System.out.println("Retrieving newest data...");
+ alt = p.getData(0);
+ System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+ file = arg[0]+"-new.log";
+ System.out.println("Saving data to "+file+"...");
+ savefile(file,alt);
+
+
+ System.out.println("Retrieving medium data...");
+ alt = p.getData(1);
+ System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+ file = arg[0]+"-med.log";
+ System.out.println("Saving data to "+file+"...");
+ savefile(file,alt);
+
+
+ System.out.println("Retrieving oldest data...");
+ alt = p.getData(2);
+ System.out.println("Apogee at "+alt.getApogee()+" feet");
+
+ file = arg[0]+"-old.log";
+ System.out.println("Saving data to "+file+"...");
+ savefile(file,alt);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (PortInUseException e) {
+ e.printStackTrace();
+ }
+
+// System.out.println(alt);
+// alt.printData();
+
+ }
+
+
+ static private void savefile(String file, AltData data) throws FileNotFoundException {
+
+ PrintStream output = new PrintStream(file);
+
+ // WTF is this so difficult?!?
+ DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
+ TimeZone tz=TimeZone.getTimeZone("GMT+3");
+ fmt.setTimeZone(tz);
+
+ output.println("# Alt15K data, file "+file);
+ output.println("# Data retrieved at: "+fmt.format(new Date()));
+ output.println("# Values are in feet above launch level");
+ output.println("# ");
+ output.println("# Apogee = "+data.getApogee());
+ output.println("# MSL level = "+data.getMslLevel());
+ output.println("# Data count = "+data.getDataSamples());
+
+ byte[] b = data.getVersion();
+ String s="";
+ for (int i=0; i<b.length; i++) {
+ if (s.equals(""))
+ s = ""+((int)b[i]);
+ else
+ s = s+"."+((int)b[i]);
+ }
+ output.println("# Altimeter version = " + s);
+
+ int[] values = data.getData();
+ for (int i=0; i < values.length; i++) {
+ output.println(""+values[i]);
+ }
+
+ output.close();
+ }
+
+
+ static private void printBytes(String str, byte[] b) {
+ printBytes(str, b,b.length);
+ }
+
+ static private void printBytes(String str, byte[] b, int n) {
+ String s;
+ s = str+" "+n+" bytes:";
+ for (int i=0; i<n; i++) {
+ s += " "+unsign(b[i]);
+ }
+ System.out.println(s);
+ }
+
+ static private int unsign(byte b) {
+ if (b >= 0)
+ return b;
+ else
+ return 256 + b;
+ }
+
+ @SuppressWarnings("unused")
+ static private int combine(int a, int b) {
+ return 256*a + b;
+ }
+
+ static private int combine(byte a, byte b) {
+ int val = 256*unsign(a)+unsign(b);
+ if (val <= 32767)
+ return val;
+ else
+ return val-65536;
+
+ }
+
+}
--- /dev/null
+package altimeter;
+
+public class AltData {
+
+ private int mslLevel = 0;
+ private int samples = 0;
+ private int[] data = null;
+ private byte[] version = null;
+
+
+ public void setMslLevel(int msl) {
+ mslLevel = msl;
+ }
+ public int getMslLevel() {
+ return mslLevel;
+ }
+
+ public void setDataSamples(int s) {
+ samples = s;
+ }
+ public int getDataSamples() {
+ return samples;
+ }
+
+ public void setVersion(byte[] v) {
+ if (v==null)
+ version = null;
+ else
+ version = v.clone();
+ }
+ public byte[] getVersion() {
+ if (version == null)
+ return null;
+ return version.clone();
+ }
+
+ public void setData(int[] data) {
+ if (data==null)
+ this.data = null;
+ else
+ this.data = data.clone();
+ }
+ public int[] getData() {
+ if (data == null)
+ return null;
+ return data.clone();
+ }
+
+ public int getApogee() {
+ if (data == null || data.length==0)
+ return 0;
+ int max = Integer.MIN_VALUE;
+ for (int i=0; i<data.length; i++) {
+ if (data[i] > max)
+ max = data[i];
+ }
+ return max;
+ }
+
+ @Override
+ public String toString() {
+ String s = "AltData(";
+ s += "MSL:"+getMslLevel()+",";
+ s += "Apogee:"+getApogee()+",";
+ s += "Samples:"+getDataSamples();
+ s += ")";
+ return s;
+ }
+
+ public void printData() {
+ System.out.println(toString()+":");
+ for (int i=0; i<data.length; i+=8) {
+ String s = " "+i+":";
+ for (int j=0; j<8 && (i+j)<data.length; j++) {
+ s += " "+data[i+j];
+ }
+ System.out.println(s);
+ }
+ }
+
+}
--- /dev/null
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ *
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class RotationLogger {
+ private static final boolean DEBUG = false;
+
+ private static final int BYTES = 65536;
+
+
+ private final CommPortIdentifier portID;
+ private SerialPort port = null;
+ private InputStream is = null;
+ private OutputStream os = null;
+
+
+
+ @SuppressWarnings("unchecked")
+ public static String[] getNames() {
+ ArrayList<String> list = new ArrayList<String>();;
+
+ Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+ while (pids.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+ if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+ list.add(pid.getName());
+ }
+ return list.toArray(new String[0]);
+ }
+
+
+
+
+
+ @SuppressWarnings("unchecked")
+ public RotationLogger(String name) throws IOException {
+ CommPortIdentifier portID = null;
+
+ Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+ while (portIdentifiers.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+
+ if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+ pid.getName().equals(name)) {
+ portID = pid;
+ break;
+ }
+ }
+
+ if (portID==null) {
+ throw new IOException("Port '"+name+"' not found.");
+ }
+ this.portID = portID;
+ }
+
+
+
+
+
+
+ public void readData() throws IOException, PortInUseException {
+ int c;
+
+ int[] data = new int[BYTES];
+
+ FileOutputStream rawdump = null;
+
+
+ try {
+ open();
+
+ System.err.println("Sending dump mode command...");
+
+ for (int i=0; i<16; i++) {
+ os.write('D');
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException ignore) { }
+ }
+
+ System.err.println("Waiting for response...");
+ while (true) {
+ c = is.read();
+ if (c == 'K') {
+ break;
+ } else {
+ System.err.printf("Received spurious c=%d\n",c);
+ }
+ }
+
+ System.err.println("Received response.");
+
+
+
+ System.err.println("Opening 'rawdump'...");
+ rawdump = new FileOutputStream("rawdump");
+
+
+
+ System.err.println("Performing dump...");
+
+ os.write('A');
+
+ byte[] buffer = new byte[1024];
+ int printCount = 0;
+ for (int count=0; count < BYTES; ) {
+ if ((BYTES-count) < buffer.length) {
+ buffer = new byte[BYTES-count];
+ }
+
+ int n = is.read(buffer);
+ if (n < 0) {
+ System.err.println("Error condition, n="+n);
+ return;
+ }
+
+ rawdump.write(buffer, 0, n);
+
+ for (int i=0; i<n; i++) {
+ data[count+i] = unsign(buffer[i]);
+ }
+ count += n;
+ if (count - printCount > 1024) {
+ System.err.println("Read "+count+" bytes...");
+ printCount = count;
+ }
+ }
+
+
+ System.err.println("Verifying checksum...");
+ int reported = is.read();
+
+ byte computed = 0;
+ for (int i=0; i < data.length; i++) {
+ computed += data[i];
+ }
+ if (computed == reported) {
+ System.err.println("Checksum ok ("+computed+")");
+ } else {
+ System.err.println("Error in checksum, computed="+computed+
+ " reported="+reported);
+ }
+
+ System.err.println("Communication done.");
+
+ } catch (UnsupportedCommOperationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ close();
+ if (rawdump != null)
+ rawdump.close();
+ }
+
+ convertData(data);
+
+ }
+
+
+
+ //////////// Data interpretation //////////////
+
+
+ private static void convertData(int[] data) {
+
+ System.err.println("Converting data...");
+
+ int lastBuffer = data[0xffff];
+ if (lastBuffer < 0 || lastBuffer > 3) {
+ System.err.println("Illegal last accessed buffer: "+lastBuffer);
+ return;
+ }
+ System.err.println("Last used buffer: "+lastBuffer);
+
+ for (int i=4; i>0; i--) {
+ int n = (lastBuffer + i) % 4;
+ int bufNumber = 4-i;
+
+ convertBuffer(data, n * (BYTES/4), bufNumber);
+ }
+
+ }
+
+
+ private static void convertBuffer(int[] data, int position, int bufNumber) {
+ int startPosition;
+
+ startPosition = data[position + 0xfd] << 8 + data[position+0xfe];
+
+ // 50 samples per 128 bytes
+ int startTime = (startPosition -position) * 50 / 128;
+
+ System.err.println(" Buffer "+ bufNumber + " (at position "+position+")...");
+ System.err.println(" Start position "+startPosition+" time "+startTime);
+
+ System.out.println("# Buffer "+bufNumber);
+ System.out.println("# Start position t="+startTime);
+
+
+ int t = 0;
+ for (int page = 0; page < 128; page++) {
+ int pageStart = position + page * 128;
+
+ if (pageStart == startPosition) {
+ System.out.println("# ---clip---");
+ }
+
+ for (int i=0; i<125; i += 5) {
+ int sample1, sample2;
+
+ int start = pageStart + i;
+// System.err.println("page="+page+" i="+i+
+// " position="+position+" pageStart="+pageStart+" start="+start);
+
+ sample1 = (data[start] << 2) + (data[start+1] >> 6);
+ sample2 = ((data[start+1] & 0x3f) << 4) + (data[start+2] >> 4);
+ System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2);
+ t++;
+
+ sample1 = ((data[start+2] & 0x0f) << 6) + (data[start+3] >> 2);
+ sample2 = ((data[start+3] & 3) << 8) + data[start+4];
+ System.out.printf("%d %4d %4d %4d\n", bufNumber, t, sample1, sample2);
+ t++;
+ }
+ }
+ }
+
+
+
+ private void open() throws PortInUseException, IOException,
+ UnsupportedCommOperationException {
+
+ if (port != null) {
+ System.err.println("ERROR: open() called with port="+port);
+ Thread.dumpStack();
+ close();
+ }
+
+ if (DEBUG) {
+ System.err.println(" Opening port...");
+ }
+
+ port = (SerialPort)portID.open("OpenRocket",1000);
+
+ port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
+ SerialPort.PARITY_NONE);
+
+ port.setInputBufferSize(1);
+ port.setOutputBufferSize(1);
+
+ is = port.getInputStream();
+ os = port.getOutputStream();
+ }
+
+
+ private void close() {
+ if (DEBUG)
+ System.err.println(" Closing port");
+
+ SerialPort p = port;
+ port = null;
+ is = null;
+ if (p != null)
+ p.close();
+ }
+
+
+
+ private static int unsign(byte b) {
+ if (b >= 0)
+ return b;
+ else
+ return 256 + b;
+ }
+
+
+
+
+ public static void main(String[] arg) throws Exception {
+
+ if (arg.length > 2) {
+ System.err.println("Illegal arguments.");
+ return;
+ }
+ if (arg.length == 1) {
+ FileInputStream is = new FileInputStream(arg[0]);
+ byte[] buffer = new byte[BYTES];
+ int n = is.read(buffer);
+ if (n != BYTES) {
+ System.err.println("Could read only "+n+" bytes");
+ return;
+ }
+
+ int[] data = new int[BYTES];
+ for (int i=0; i<BYTES; i++) {
+ data[i] = unsign(buffer[i]);
+ }
+
+ int checksum=0;
+ for (int i=0; i<BYTES; i++) {
+ checksum += data[i];
+ }
+ checksum = checksum%256;
+ System.err.println("Checksum: "+checksum);
+
+ convertData(data);
+ return;
+ }
+
+
+ String device = null;
+ String[] devices = RotationLogger.getNames();
+ for (int i=0; i<devices.length; i++) {
+ if (devices[i].matches(".*USB.*")) {
+ device = devices[i];
+ break;
+ }
+ }
+ if (device == null) {
+ System.err.println("Device not found.");
+ return;
+ }
+
+
+ System.err.println("Selected device "+device);
+
+
+ RotationLogger p = new RotationLogger(device);
+
+ p.readData();
+
+ }
+
+
+}
--- /dev/null
+package altimeter;
+
+import gnu.io.CommPortIdentifier;
+import gnu.io.PortInUseException;
+import gnu.io.SerialPort;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Class to interface the PerfectFlite Alt15K/WD altimeter.
+ *
+ * Also includes a main method that retrieves all flight profiles and saves them to files.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class SerialDownload {
+ private static final boolean DEBUG = false;
+
+ private static final int MAGIC = 666;
+
+ private final CommPortIdentifier portID;
+ private SerialPort port = null;
+ private InputStream is = null;
+
+
+
+ @SuppressWarnings("unchecked")
+ public static String[] getNames() {
+ ArrayList<String> list = new ArrayList<String>();;
+
+ Enumeration pids = CommPortIdentifier.getPortIdentifiers();
+
+ while (pids.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) pids.nextElement();
+
+ if (pid.getPortType() == CommPortIdentifier.PORT_SERIAL)
+ list.add(pid.getName());
+ }
+ return list.toArray(new String[0]);
+ }
+
+
+
+
+
+ @SuppressWarnings("unchecked")
+ public SerialDownload(String name) throws IOException {
+ CommPortIdentifier portID = null;
+
+ Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
+ while (portIdentifiers.hasMoreElements()) {
+ CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
+
+ if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
+ pid.getName().equals(name)) {
+ portID = pid;
+ break;
+ }
+ }
+
+ if (portID==null) {
+ throw new IOException("Port '"+name+"' not found.");
+ }
+ this.portID = portID;
+ }
+
+
+
+
+
+
+ public void readData() throws IOException, PortInUseException {
+ long t0 = -1;
+ long t;
+
+ int previous = MAGIC;
+
+
+ try {
+ open();
+
+ System.err.println("Ready to read...");
+ while (true) {
+ int c = is.read();
+ t = System.nanoTime();
+ if (t0 < 0)
+ t0 = t;
+
+ System.out.printf("%10.6f %d\n", ((double)t-t0)/1000000000.0, c);
+
+ if (previous == MAGIC) {
+ previous = c;
+ } else {
+ System.out.printf("# Altitude: %5d\n", previous*256 + c);
+ previous = MAGIC;
+ }
+
+ if (c < 0)
+ break;
+ }
+
+
+ } catch (UnsupportedCommOperationException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ close();
+ }
+ }
+
+
+
+ private void open() throws PortInUseException, IOException,
+ UnsupportedCommOperationException {
+
+ if (port != null) {
+ System.err.println("ERROR: open() called with port="+port);
+ Thread.dumpStack();
+ close();
+ }
+
+ if (DEBUG) {
+ System.err.println(" Opening port...");
+ }
+
+ port = (SerialPort)portID.open("OpenRocket",1000);
+
+ port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
+ SerialPort.PARITY_NONE);
+
+ port.setInputBufferSize(1);
+ port.setOutputBufferSize(1);
+
+ is = port.getInputStream();
+ }
+
+
+ private void close() {
+ if (DEBUG)
+ System.err.println(" Closing port");
+
+ SerialPort p = port;
+ port = null;
+ is = null;
+ if (p != null)
+ p.close();
+ }
+
+
+
+
+
+ public static void main(String[] arg) throws Exception {
+
+ String device = null;
+ String[] devices = SerialDownload.getNames();
+ for (int i=0; i<devices.length; i++) {
+ if (devices[i].matches(".*USB.*")) {
+ device = devices[i];
+ break;
+ }
+ }
+ if (device == null) {
+ System.err.println("Device not found.");
+ return;
+ }
+
+
+ System.err.println("Selected device "+device);
+
+
+ SerialDownload p = new SerialDownload(device);
+
+ p.readData();
+
+ }
+
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+
+import net.sf.openrocket.util.Prefs;
+
+public class BugReporter extends Communicator {
+
+ // Inhibit instantiation
+ private BugReporter() {
+ }
+
+
+ /**
+ * Send the provided report to the OpenRocket bug report URL. If the connection
+ * fails or the server does not respond with the correct response code, an
+ * exception is thrown.
+ *
+ * @param report the report to send.
+ * @throws IOException if an error occurs while connecting to the server or
+ * the server responds with a wrong response code.
+ */
+ public static void sendBugReport(String report) throws IOException {
+
+ HttpURLConnection connection = connectionSource.getConnection(BUG_REPORT_URL);
+
+ connection.setConnectTimeout(CONNECTION_TIMEOUT);
+ connection.setInstanceFollowRedirects(true);
+ connection.setRequestMethod("POST");
+ connection.setUseCaches(false);
+ connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
+
+ String post;
+ post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
+ + "&" + BUG_REPORT_PARAM + "=" + encode(report));
+
+ OutputStreamWriter wr = null;
+ try {
+ // Send post information
+ connection.setDoOutput(true);
+ wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
+ wr.write(post);
+ wr.flush();
+
+ if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
+ throw new IOException("Server responded with code " +
+ connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
+ }
+ } finally {
+ if (wr != null)
+ wr.close();
+ connection.disconnect();
+ }
+ }
+
+}
+++ /dev/null
-package net.sf.openrocket.communication;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-
-import net.sf.openrocket.util.ComparablePair;
-import net.sf.openrocket.util.Prefs;
-
-public class Communication {
-
- private static final String BUG_REPORT_URL =
- "http://openrocket.sourceforge.net/actions/reportbug";
- private static final String UPDATE_INFO_URL =
- "http://openrocket.sourceforge.net/actions/updates";
-
- private static final String VERSION_PARAM = "version";
-
-
- private static final String BUG_REPORT_PARAM = "content";
- private static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
- private static final int CONNECTION_TIMEOUT = 10000; // in milliseconds
-
- private static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
- private static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
- private static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
-
-
- private static UpdateInfoFetcher fetcher = null;
-
-
- /**
- * Send the provided report to the OpenRocket bug report URL. If the connection
- * fails or the server does not respond with the correct response code, an
- * exception is thrown.
- *
- * @param report the report to send.
- * @throws IOException if an error occurs while connecting to the server or
- * the server responds with a wrong response code.
- */
- public static void sendBugReport(String report) throws IOException {
- URL url = new URL(BUG_REPORT_URL);
-
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
- connection.setConnectTimeout(CONNECTION_TIMEOUT);
- connection.setInstanceFollowRedirects(true);
- connection.setRequestMethod("POST");
- connection.setUseCaches(false);
- connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
-
- String post;
- post = (VERSION_PARAM + "=" + encode(Prefs.getVersion())
- + "&" + BUG_REPORT_PARAM + "=" + encode(report));
-
- OutputStreamWriter wr = null;
- try {
- // Send post information
- connection.setDoOutput(true);
- wr = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
- wr.write(post);
- wr.flush();
-
- if (connection.getResponseCode() != BUG_REPORT_RESPONSE_CODE) {
- throw new IOException("Server responded with code " +
- connection.getResponseCode() + ", expecting " + BUG_REPORT_RESPONSE_CODE);
- }
- } finally {
- if (wr != null)
- wr.close();
- connection.disconnect();
- }
- }
-
-
-
- /**
- * Start an asynchronous task that will fetch information about the latest
- * OpenRocket version. This will overwrite any previous fetching operation.
- */
- public static void startFetchUpdateInfo() {
- fetcher = new UpdateInfoFetcher();
- fetcher.start();
- }
-
-
- /**
- * Check whether the update info fetching is still in progress.
- *
- * @return <code>true</code> if the communication is still in progress.
- */
- public static boolean isFetchUpdateInfoRunning() {
- if (fetcher == null) {
- throw new IllegalStateException("startFetchUpdateInfo() has not been called");
- }
- return fetcher.isAlive();
- }
-
-
- /**
- * Retrieve the result of the background update info fetcher. This method returns
- * the result of the previous call to {@link #startFetchUpdateInfo()}. It must be
- * called before calling this method.
- * <p>
- * This method will return <code>null</code> if the info fetcher is still running or
- * if it encountered a problem in communicating with the server. The difference can
- * be checked using {@link #isFetchUpdateInfoRunning()}.
- *
- * @return the update result, or <code>null</code> if the fetching is still in progress
- * or an error occurred while communicating with the server.
- * @throws IllegalStateException if {@link #startFetchUpdateInfo()} has not been called.
- */
- public static UpdateInfo getUpdateInfo() {
- if (fetcher == null) {
- throw new IllegalStateException("startFetchUpdateInfo() has not been called");
- }
- return fetcher.info;
- }
-
-
-
- /**
- * Parse the data received from the server.
- *
- * @param r the Reader from which to read.
- * @return an UpdateInfo construct, or <code>null</code> if the data was invalid.
- * @throws IOException if an I/O exception occurs.
- */
- /* package-private */
- static UpdateInfo parseUpdateInput(Reader r) throws IOException {
- BufferedReader reader;
- if (r instanceof BufferedReader) {
- reader = (BufferedReader)r;
- } else {
- reader = new BufferedReader(r);
- }
-
-
- String version = null;
- ArrayList<ComparablePair<Integer,String>> updates =
- new ArrayList<ComparablePair<Integer,String>>();
-
- String str = reader.readLine();
- while (str != null) {
- if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
- version = str.substring(8).trim();
- } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
- int index = str.indexOf(':');
- int value = Integer.parseInt(str.substring(0, index));
- String desc = str.substring(index+1).trim();
- if (!desc.equals("")) {
- updates.add(new ComparablePair<Integer,String>(value, desc));
- }
- }
- // Ignore anything else
- str = reader.readLine();
- }
-
- if (version != null) {
- return new UpdateInfo(version, updates);
- } else {
- return null;
- }
- }
-
-
-
-
- private static class UpdateInfoFetcher extends Thread {
-
- private volatile UpdateInfo info = null;
-
- @Override
- public void run() {
- try {
- doConnection();
- } catch (IOException e) {
- return;
- }
- }
-
-
- private void doConnection() throws IOException {
- URL url;
- url = new URL(UPDATE_INFO_URL + "?" + VERSION_PARAM + "=" +
- encode(Prefs.getVersion()));
-
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
- connection.setConnectTimeout(CONNECTION_TIMEOUT);
- connection.setInstanceFollowRedirects(true);
- connection.setRequestMethod("GET");
- connection.setUseCaches(false);
- connection.setRequestProperty("X-OpenRocket-Version", encode(Prefs.getVersion()));
- connection.setRequestProperty("X-OpenRocket-ID", encode(Prefs.getUniqueID()));
- connection.setRequestProperty("X-OpenRocket-OS", encode(
- System.getProperty("os.name") + " " + System.getProperty("os.arch")));
- connection.setRequestProperty("X-OpenRocket-Java", encode(
- System.getProperty("java.vendor") + " " + System.getProperty("java.version")));
- connection.setRequestProperty("X-OpenRocket-Country", encode(
- System.getProperty("user.country")));
-
- InputStream is = null;
- try {
- connection.connect();
-
- if (connection.getResponseCode() == UPDATE_INFO_NO_UPDATE_CODE) {
- // No updates are available
- info = new UpdateInfo();
- return;
- }
-
- if (connection.getResponseCode() != UPDATE_INFO_UPDATE_AVAILABLE) {
- // Error communicating with server
- return;
- }
-
- if (!UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(connection.getContentType())) {
- // Unknown response type
- return;
- }
-
- // Update is available, parse input
- is = connection.getInputStream();
- String encoding = connection.getContentEncoding();
- if (encoding == null)
- encoding = "UTF-8";
- BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
-
-
-
- } finally {
- if (is != null)
- is.close();
- connection.disconnect();
- }
-
-
- }
-
- }
-
-
- private static String encode(String str) {
- if (str == null)
- return "null";
- try {
- return URLEncoder.encode(str, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Unsupported encoding UTF-8", e);
- }
- }
-
-}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+
+public abstract class Communicator {
+
+ protected static final String BUG_REPORT_URL;
+
+ protected static final String UPDATE_INFO_URL;
+
+ static {
+ String url;
+ url = System.getProperty("openrocket.debug.bugurl");
+ if (url == null)
+ url = "http://openrocket.sourceforge.net/actions/reportbug";
+ BUG_REPORT_URL = url;
+
+ url = System.getProperty("openrocket.debug.updateurl");
+ if (url == null)
+ url = "http://openrocket.sourceforge.net/actions/updates";
+ UPDATE_INFO_URL = url;
+ }
+
+
+ protected static final String VERSION_PARAM = "version";
+
+
+ protected static final String BUG_REPORT_PARAM = "content";
+ protected static final int BUG_REPORT_RESPONSE_CODE = HttpURLConnection.HTTP_ACCEPTED;
+ protected static final int CONNECTION_TIMEOUT = 10000; // in milliseconds
+
+ protected static final int UPDATE_INFO_UPDATE_AVAILABLE = HttpURLConnection.HTTP_OK;
+ protected static final int UPDATE_INFO_NO_UPDATE_CODE = HttpURLConnection.HTTP_NO_CONTENT;
+ protected static final String UPDATE_INFO_CONTENT_TYPE = "text/plain";
+
+ // Limit the number of bytes that can be read from the server
+ protected static final int MAX_INPUT_BYTES = 20000;
+
+
+ protected static ConnectionSource connectionSource = new DefaultConnectionSource();
+
+
+ /**
+ * Set the source of the network connections. This can be used for unit testing.
+ * By default the source is a DefaultConnectionSource.
+ *
+ * @param source the source of the connections.
+ */
+ public static void setConnectionSource(ConnectionSource source) {
+ connectionSource = source;
+ }
+
+
+ /**
+ * URL-encode the specified string in UTF-8 encoding.
+ *
+ * @param str the string to encode (null ok)
+ * @return the encoded string or "null"
+ */
+ public static String encode(String str) {
+ if (str == null)
+ return "null";
+ try {
+ return URLEncoder.encode(str, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Unsupported encoding UTF-8", e);
+ }
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+/**
+ * A source for network connections. This interface exists to enable unit testing.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public interface ConnectionSource {
+
+ /**
+ * Return a connection to the specified url.
+ * @param url the URL to connect to.
+ * @return the corresponding HttpURLConnection
+ * @throws IOException if an IOException occurs
+ */
+ public HttpURLConnection getConnection(String url) throws IOException;
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Default implementation of ConnectionSource, which simply opens a new
+ * HttpURLConnection from a URL object.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class DefaultConnectionSource implements ConnectionSource {
+
+ @Override
+ public HttpURLConnection getConnection(String urlString) throws IOException {
+ URL url = new URL(urlString);
+ return (HttpURLConnection) url.openConnection();
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.LimitedInputStream;
+import net.sf.openrocket.util.Prefs;
+
+public class UpdateInfoRetriever {
+
+ private UpdateInfoFetcher fetcher = null;
+
+
+ /**
+ * Start an asynchronous task that will fetch information about the latest
+ * OpenRocket version. This will overwrite any previous fetching operation.
+ * This call will return immediately.
+ */
+ public void start() {
+ fetcher = new UpdateInfoFetcher();
+ fetcher.setDaemon(true);
+ fetcher.start();
+ }
+
+
+ /**
+ * Check whether the update info fetching is still in progress.
+ *
+ * @return <code>true</code> if the communication is still in progress.
+ */
+ public boolean isRunning() {
+ if (fetcher == null) {
+ throw new IllegalStateException("startFetchUpdateInfo() has not been called");
+ }
+ return fetcher.isAlive();
+ }
+
+
+ /**
+ * Retrieve the result of the background update info fetcher. This method returns
+ * the result of the previous call to {@link #start()}. It must be
+ * called before calling this method.
+ * <p>
+ * This method will return <code>null</code> if the info fetcher is still running or
+ * if it encountered a problem in communicating with the server. The difference can
+ * be checked using {@link #isRunning()}.
+ *
+ * @return the update result, or <code>null</code> if the fetching is still in progress
+ * or an error occurred while communicating with the server.
+ * @throws IllegalStateException if {@link #start()} has not been called.
+ */
+ public UpdateInfo getUpdateInfo() {
+ if (fetcher == null) {
+ throw new IllegalStateException("start() has not been called");
+ }
+ return fetcher.info;
+ }
+
+
+
+ /**
+ * Parse the data received from the server.
+ *
+ * @param r the Reader from which to read.
+ * @return an UpdateInfo construct, or <code>null</code> if the data was invalid.
+ * @throws IOException if an I/O exception occurs.
+ */
+ /* package-private */
+ static UpdateInfo parseUpdateInput(Reader r) throws IOException {
+ BufferedReader reader;
+ if (r instanceof BufferedReader) {
+ reader = (BufferedReader)r;
+ } else {
+ reader = new BufferedReader(r);
+ }
+
+
+ String version = null;
+ ArrayList<ComparablePair<Integer,String>> updates =
+ new ArrayList<ComparablePair<Integer,String>>();
+
+ String str = reader.readLine();
+ while (str != null) {
+ if (str.matches("^Version: *[0-9]+\\.[0-9]+\\.[0-9]+[a-zA-Z0-9.-]* *$")) {
+ version = str.substring(8).trim();
+ } else if (str.matches("^[0-9]+:\\p{Print}+$")) {
+ int index = str.indexOf(':');
+ int value = Integer.parseInt(str.substring(0, index));
+ String desc = str.substring(index+1).trim();
+ if (!desc.equals("")) {
+ updates.add(new ComparablePair<Integer,String>(value, desc));
+ }
+ }
+ // Ignore anything else
+ str = reader.readLine();
+ }
+
+ if (version != null) {
+ return new UpdateInfo(version, updates);
+ } else {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * An asynchronous task that fetches and parses the update info.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+ private class UpdateInfoFetcher extends Thread {
+
+ private volatile UpdateInfo info = null;
+
+ @Override
+ public void run() {
+ try {
+ doConnection();
+ } catch (IOException e) {
+ return;
+ }
+ }
+
+
+ private void doConnection() throws IOException {
+ String url = Communicator.UPDATE_INFO_URL + "?" + Communicator.VERSION_PARAM + "="
+ + Communicator.encode(Prefs.getVersion());
+
+ HttpURLConnection connection = Communicator.connectionSource.getConnection(url);
+
+ connection.setConnectTimeout(Communicator.CONNECTION_TIMEOUT);
+ connection.setInstanceFollowRedirects(true);
+ connection.setRequestMethod("GET");
+ connection.setUseCaches(false);
+ connection.setDoInput(true);
+ connection.setRequestProperty("X-OpenRocket-Version",
+ Communicator.encode(Prefs.getVersion()));
+ connection.setRequestProperty("X-OpenRocket-ID",
+ Communicator.encode(Prefs.getUniqueID()));
+ connection.setRequestProperty("X-OpenRocket-OS",
+ Communicator.encode(System.getProperty("os.name") + " " +
+ System.getProperty("os.arch")));
+ connection.setRequestProperty("X-OpenRocket-Java",
+ Communicator.encode(System.getProperty("java.vendor") + " " +
+ System.getProperty("java.version")));
+ connection.setRequestProperty("X-OpenRocket-Country",
+ Communicator.encode(System.getProperty("user.country") + " " +
+ Communicator.encode(System.getProperty("user.timezone"))));
+
+ InputStream is = null;
+ try {
+ connection.connect();
+
+ if (connection.getResponseCode() == Communicator.UPDATE_INFO_NO_UPDATE_CODE) {
+ // No updates are available
+ info = new UpdateInfo();
+ return;
+ }
+
+ if (connection.getResponseCode() != Communicator.UPDATE_INFO_UPDATE_AVAILABLE) {
+ // Error communicating with server
+ return;
+ }
+
+ if (!Communicator.UPDATE_INFO_CONTENT_TYPE.equalsIgnoreCase(
+ connection.getContentType())) {
+ // Unknown response type
+ return;
+ }
+
+
+ // Update is available, parse input
+ is = connection.getInputStream();
+ is = new LimitedInputStream(is, Communicator.MAX_INPUT_BYTES);
+ String encoding = connection.getContentEncoding();
+ if (encoding == null || encoding.equals(""))
+ encoding = "UTF-8";
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
+
+ String version = null;
+ ArrayList<ComparablePair<Integer, String>> updates =
+ new ArrayList<ComparablePair<Integer, String>>();
+
+ String line = reader.readLine();
+ while (line != null) {
+
+ if (line.matches("^Version:[a-zA-Z0-9._ -]{1,30}$")) {
+ version = line.substring(8).trim();
+ } else if (line.matches("^[0-9]{1,9}:\\P{Cntrl}{1,300}$")) {
+ String[] split = line.split(":", 2);
+ int n = Integer.parseInt(split[0]);
+ updates.add(new ComparablePair<Integer,String>(n, split[1].trim()));
+ }
+ // Ignore line otherwise
+ line = reader.readLine();
+ }
+
+ // Check version input
+ if (version == null || version.length() == 0 ||
+ version.equalsIgnoreCase(Prefs.getVersion())) {
+ // Invalid response
+ return;
+ }
+
+
+ info = new UpdateInfo(version, updates);
+
+ } finally {
+ try {
+ if (is != null)
+ is.close();
+ connection.disconnect();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
BULK_MATERIAL.add(new Material.Bulk("Cardboard", 680, false));
BULK_MATERIAL.add(new Material.Bulk("Carbon fiber", 1780, false));
BULK_MATERIAL.add(new Material.Bulk("Cork", 240, false));
+ BULK_MATERIAL.add(new Material.Bulk("Depron", 40, false));
BULK_MATERIAL.add(new Material.Bulk("Fiberglass", 1850, false));
BULK_MATERIAL.add(new Material.Bulk("Kraft phenolic",950, false));
BULK_MATERIAL.add(new Material.Bulk("Maple", 755, false));
BULK_MATERIAL.add(new Material.Bulk("Polystyrene", 1050, false));
BULK_MATERIAL.add(new Material.Bulk("PVC", 1390, false));
BULK_MATERIAL.add(new Material.Bulk("Spruce", 450, false));
+ // TODO: CRITICAL: Add styrofoam
BULK_MATERIAL.add(new Material.Bulk("Quantum tubing",1050, false));
SURFACE_MATERIAL.add(new Material.Surface("Ripstop nylon", 0.067, false));
}
+ /*
+ * Used just for ensuring initialization of the class.
+ */
+ public static void fakeMethod() {
+
+ }
+
+
/**
* Find a material from the database with the specified type and name. Returns
* <code>null</code> if the specified material could not be found.
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+
+/**
+ * A JLabel that limits the minimum and maximum height of the label to the
+ * initial preferred height of the label. This is required in labels that use HTML
+ * since these often cause the panels to expand too much in height.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class HtmlLabel extends JLabel {
+
+ public HtmlLabel() {
+ super();
+ limitSize();
+ }
+
+ public HtmlLabel(String text) {
+ super(text);
+ limitSize();
+ }
+
+ public HtmlLabel(String text, int horizontalAlignment) {
+ super(text, horizontalAlignment);
+ limitSize();
+ }
+
+
+ private void limitSize() {
+ Dimension dim = this.getPreferredSize();
+ this.setMinimumSize(new Dimension(0, dim.height));
+ this.setMaximumSize(new Dimension(Integer.MAX_VALUE, dim.height));
+ }
+
+}
+++ /dev/null
-package net.sf.openrocket.gui.components;
-
-import java.awt.Font;
-import javax.swing.JLabel;
-
-/**
- * A resizeable JLabel. The method resizeFont(float) changes the current font size by the
- * given (positive or negative) amount. The change is relative to the current font size.
- * <p>
- * A nice small text is achievable by <code>new ResizeLabel("My text", -2);</code>
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-
-public class ResizeLabel extends JLabel {
-
- public ResizeLabel() {
- super();
- }
-
- public ResizeLabel(String text) {
- super(text);
- }
-
- public ResizeLabel(float size) {
- super();
- resizeFont(size);
- }
-
- public ResizeLabel(String text, float size) {
- super(text);
- resizeFont(size);
- }
-
- public ResizeLabel(String text, int horizontalAlignment, float size) {
- super(text, horizontalAlignment);
- resizeFont(size);
- }
-
-
- public void resizeFont(float size) {
- Font font = this.getFont();
- font = font.deriveFont(font.getSize2D()+size);
- this.setFont(font);
- }
-
-}
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Font;
+
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+/**
+ * A resizeable and styleable JLabel. The method {@link #resizeFont(float)} changes the
+ * current font size by the given (positive or negative) amount. The change is relative
+ * to the current font size. The method {@link #setFontStyle(Style)} sets the style
+ * (bold/italic) of the font.
+ * <p>
+ * A nice small text is achievable by <code>new ResizeLabel("My text", -2);</code>
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+
+public class StyledLabel extends JLabel {
+
+ public enum Style {
+ PLAIN(Font.PLAIN),
+ BOLD(Font.BOLD),
+ ITALIC(Font.ITALIC),
+ BOLD_ITALIC(Font.BOLD | Font.ITALIC);
+
+ private int style;
+ Style(int fontStyle) {
+ this.style = fontStyle;
+ }
+ public int getFontStyle() {
+ return style;
+ }
+ }
+
+
+
+ public StyledLabel() {
+ this("", SwingConstants.LEADING, 0f);
+ }
+
+ public StyledLabel(String text) {
+ this(text, SwingConstants.LEADING, 0f);
+ }
+
+ public StyledLabel(float size) {
+ this("", SwingConstants.LEADING, size);
+ }
+
+ public StyledLabel(String text, float size) {
+ this(text, SwingConstants.LEADING, size);
+ }
+
+ public StyledLabel(String text, int horizontalAlignment, float size) {
+ super(text, horizontalAlignment);
+ resizeFont(size);
+ checkPreferredSize(size, Style.PLAIN);
+ }
+
+
+
+ public StyledLabel(Style style) {
+ this("", SwingConstants.LEADING, 0f, style);
+ }
+
+ public StyledLabel(String text, Style style) {
+ this(text, SwingConstants.LEADING, 0f, style);
+ }
+
+ public StyledLabel(float size, Style style) {
+ this("", SwingConstants.LEADING, size, style);
+ }
+
+ public StyledLabel(String text, float size, Style style) {
+ this(text, SwingConstants.LEADING, size, style);
+ }
+
+ public StyledLabel(String text, int horizontalAlignment, float size, Style style) {
+ super(text, horizontalAlignment);
+ resizeFont(size);
+ setFontStyle(style);
+ checkPreferredSize(size, style);
+ }
+
+
+
+
+ private void checkPreferredSize(float size, Style style) {
+ String str = this.getText();
+ if (str.startsWith("<html>") && str.indexOf("<br") < 0) {
+ StyledLabel label = new StyledLabel("plaintext", size, style);
+ label.validate();
+ System.out.println("Plain-text label: " + label.getPreferredSize());
+ System.out.println("HTML label: " + this.getPreferredSize());
+ }
+ }
+
+
+
+ public void resizeFont(float size) {
+ Font font = this.getFont();
+ font = font.deriveFont(font.getSize2D()+size);
+ this.setFont(font);
+ }
+
+ public void setFontStyle(Style style) {
+ Font font = this.getFont();
+ font = font.deriveFont(style.getFontStyle());
+ this.setFont(font);
+ }
+}
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public class UnitSelector extends ResizeLabel implements ChangeListener, MouseListener,
+public class UnitSelector extends StyledLabel implements ChangeListener, MouseListener,
ItemSelectable {
private DoubleModel model;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
// JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
// "[40lp][80lp::][30lp::][100lp::]",""));
- panel.add(new JLabel("<html><b>Through-the-wall fin tabs:</b>"), "spanx, wrap 30lp");
+ panel.add(new StyledLabel("Through-the-wall fin tabs:", Style.BOLD),
+ "spanx, wrap 30lp");
JLabel label;
DoubleModel m;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.scalefigure.FinPointFigure;
import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
panel.add(tablePane,"growy, width 100lp:100lp:, height 100lp:250lp:");
panel.add(figurePane,"gap unrel, spanx, growx, growy 1000, height 100lp:250lp:, wrap");
- panel.add(new ResizeLabel("Double-click", -2), "alignx 50%");
+ panel.add(new StyledLabel("Double-click", -2), "alignx 50%");
panel.add(new ScaleSelector(figurePane),"spany 2");
- panel.add(new ResizeLabel("Click+drag: Add and move points " +
+ panel.add(new StyledLabel("Click+drag: Add and move points " +
"Ctrl+click: Remove point", -2), "spany 2, right, wrap");
- panel.add(new ResizeLabel("to edit", -2), "alignx 50%");
+ panel.add(new StyledLabel("to edit", -2), "alignx 50%");
return panel;
}
import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.adaptors.MaterialModel;
import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.HtmlLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.MassComponent;
import net.sf.openrocket.rocketcomponent.Parachute;
//// Canopy
- panel.add(new JLabel("<html><b>Canopy:</b>"), "wrap unrel");
+ panel.add(new StyledLabel("Canopy:", Style.BOLD), "wrap unrel");
panel.add(new JLabel("Diameter:"));
// CD
- JLabel label = new JLabel("<html>Drag coefficient C<sub>D</sub>:");
+ JLabel label = new HtmlLabel("<html>Drag coefficient C<sub>D</sub>:");
String tip = "<html>The drag coefficient relative to the total area of the parachute.<br>" +
"A larger drag coefficient yields a slowed descent rate. " +
"A typical value for parachutes is 0.8.";
//// Shroud lines
- panel.add(new JLabel("<html><b>Shroud lines:</b>"), "wrap unrel");
+ panel.add(new StyledLabel("Shroud lines:", Style.BOLD), "wrap unrel");
panel.add(new JLabel("Number of lines:"));
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.adaptors.MaterialModel;
import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.HtmlLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.MassComponent;
// CD
- JLabel label = new JLabel("<html>Drag coefficient C<sub>D</sub>:");
+ JLabel label = new HtmlLabel("<html>Drag coefficient C<sub>D</sub>:");
String tip = "<html>The drag coefficient relative to the total area of the streamer.<br>" +
"A larger drag coefficient yields a slowed descent rate.";
label.setToolTipText(tip);
check.setText("Automatic");
panel.add(check,"skip, span, wrap");
- panel.add(new ResizeLabel("The drag coefficient is relative to the area of the streamer.",
+ panel.add(new StyledLabel("The drag coefficient is relative to the area of the streamer.",
-2), "span, wrap");
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.URLLabel;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.Icons;
panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")),
"spany 5, top");
- panel.add(new ResizeLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
- panel.add(new ResizeLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
+ panel.add(new StyledLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
+ panel.add(new StyledLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
String source = Prefs.getBuildSource();
if (!Prefs.DEFAULT_BUILD_SOURCE.equalsIgnoreCase(source)) {
- panel.add(new ResizeLabel("Distributed by " + source, -1),
+ panel.add(new StyledLabel("Distributed by " + source, -1),
"ax 50%, growy, wrap para");
} else {
- panel.add(new ResizeLabel(" ", -1), "ax 50%, growy, wrap para");
+ panel.add(new StyledLabel(" ", -1), "ax 50%, growy, wrap para");
}
- panel.add(new ResizeLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"),
+ panel.add(new StyledLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"),
"ax 50%, growy, wrap para");
panel.add(new URLLabel(OPENROCKET_URL), "ax 50%, growy, wrap para");
import javax.swing.JTextArea;
import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.communication.Communication;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.communication.BugReporter;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.SelectableLabel;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.JarUtil;
panel.add(new JScrollPane(textArea), "grow, wrap");
- panel.add(new ResizeLabel("The information above may be included in a public " +
+ panel.add(new StyledLabel("The information above may be included in a public " +
"bug report. Make sure it does not contain any sensitive information you " +
"do not want to be made public.", -1), "wrap para");
String text = textArea.getText();
try {
- Communication.sendBugReport(text);
+ BugReporter.sendBugReport(text);
// Success if we came here
JOptionPane.showMessageDialog(BugReportDialog.this,
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
import net.sf.openrocket.gui.components.BasicSlider;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StageSelector;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
});
- panel.add(new ResizeLabel("Reference length: ", -1),
+ panel.add(new StyledLabel("Reference length: ", -1),
"span, split, gapleft para, gapright rel");
DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
UnitSelector sel = new UnitSelector(dm, true);
sel.resizeFont(-1);
panel.add(sel, "gapright para");
- panel.add(new ResizeLabel("Reference area: ", -1), "gapright rel");
+ panel.add(new StyledLabel("Reference area: ", -1), "gapright rel");
dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
sel = new UnitSelector(dm, true);
sel.resizeFont(-1);
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.adaptors.DoubleModel;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.util.GUIUtil;
"gapleft para, span, wrap" + (note == null ? " para":""));
}
if (note != null) {
- panel.add(new ResizeLabel(note, -1), "span, wrap para");
+ panel.add(new StyledLabel(note, -1), "span, wrap para");
}
import javax.swing.JTextArea;
import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.util.GUIUtil;
public class LicenseDialog extends JDialog {
JPanel panel = new JPanel(new MigLayout("fill"));
- panel.add(new ResizeLabel("OpenRocket license", 10), "ax 50%, wrap para");
+ panel.add(new StyledLabel("OpenRocket license", 10), "ax 50%, wrap para");
String licenseText;
try {
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.database.Databases;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GUIUtil;
}
});
panel.add(delayBox,"gapright unrel");
- panel.add(new ResizeLabel("(Number of seconds or \"None\")", -1), "wrap para");
+ panel.add(new StyledLabel("(Number of seconds or \"None\")", -1), "wrap para");
setDelays(false);
--- /dev/null
+package net.sf.openrocket.gui.dialogs;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.communication.UpdateInfo;
+import net.sf.openrocket.gui.components.URLLabel;
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Icons;
+
+public class UpdateInfoDialog extends JDialog {
+
+ public UpdateInfoDialog(UpdateInfo info) {
+ super((Window)null, "OpenRocket update available", ModalityType.APPLICATION_MODAL);
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+
+ panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")),
+ "spany 100, top");
+
+
+ panel.add(new JLabel("<html><b>OpenRocket version " + info.getLatestVersion() +
+ " is available!"), "wrap para");
+
+ List<ComparablePair<Integer, String>> updates = info.getUpdates();
+ if (updates.size() > 0) {
+ panel.add(new JLabel("Updates include:"), "wrap rel");
+
+ Collections.sort(updates);
+ int count = 0;
+ int n = -1;
+ for (int i=updates.size()-1; i>=0; i--) {
+ // Add only specific number of top features
+ if (count >= 4 && n != updates.get(i).getU())
+ break;
+ n = updates.get(i).getU();
+ panel.add(new JLabel(" \u2022 " + updates.get(i).getV()), "wrap 0px");
+ count++;
+ }
+ }
+
+ panel.add(new JLabel("Download the new version from:"),
+ "gaptop para, alignx 50%, wrap unrel");
+ panel.add(new URLLabel(AboutDialog.OPENROCKET_URL), "alignx 50%, wrap para");
+
+ JButton button = new JButton("Close");
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ UpdateInfoDialog.this.dispose();
+ }
+ });
+ panel.add(button, "right");
+
+ this.add(panel);
+
+ this.pack();
+ this.setLocationRelativeTo(null);
+ GUIUtil.setDisposableDialogOptions(this, button);
+ }
+
+}
import javax.swing.JTabbedPane;
import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GUIUtil;
panel.add(button, "grow, wrap para");
- panel.add(new ResizeLabel("The effects will take place the next time you open a window.",-2),
+ panel.add(new StyledLabel("The effects will take place the next time you open a window.",-2),
"spanx, wrap");
import javax.swing.LookAndFeel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
+import javax.swing.Timer;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.communication.UpdateInfo;
+import net.sf.openrocket.communication.UpdateInfoRetriever;
+import net.sf.openrocket.database.Databases;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.file.GeneralRocketLoader;
import net.sf.openrocket.file.OpenRocketSaver;
import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
+import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
import net.sf.openrocket.gui.dialogs.WarningDialog;
import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
private static void runMain(String[] args) {
-
+
+ // Start update info fetching
+ final UpdateInfoRetriever updateInfo;
+ if (Prefs.getCheckUpdates()) {
+ updateInfo = new UpdateInfoRetriever();
+ updateInfo.start();
+ } else {
+ updateInfo = null;
+ }
+
+
+
/*
* Set the look-and-feel. On Linux, Motif/Metal is sometimes incorrectly used
* which is butt-ugly, so if the system l&f is Motif/Metal, we search for a few
// Load defaults
Prefs.loadDefaultUnits();
+
+ // Load motors etc.
+ Databases.fakeMethod();
- // Starting action
+ // Starting action (load files or open new document)
if (!handleCommandLine(args)) {
newAction();
}
+
+
+ // Check whether update info has been fetched or whether it needs more time
+ checkUpdateStatus(updateInfo);
+ }
+
+
+ private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
+ if (updateInfo == null)
+ return;
+
+ int delay = 1000;
+ if (!updateInfo.isRunning())
+ delay = 100;
+
+ final Timer timer = new Timer(delay, null);
+
+ ActionListener listener = new ActionListener() {
+ private int count = 5;
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (!updateInfo.isRunning()) {
+ timer.stop();
+
+ UpdateInfo info = updateInfo.getUpdateInfo();
+ if (info != null && !Prefs.getVersion().equals(info.getLatestVersion())) {
+ new UpdateInfoDialog(info).setVisible(true);
+ }
+ }
+ count--;
+ if (count <= 0)
+ timer.stop();
+ }
+ };
+ timer.addActionListener(listener);
+ timer.start();
}
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
import net.sf.openrocket.rocketcomponent.BodyComponent;
import net.sf.openrocket.rocketcomponent.BodyTube;
// Add labels
String[] l = text.split("\n");
for (int i=0; i<l.length; i++) {
- add(new ResizeLabel(l[i],SwingConstants.CENTER,-3.0f),"growx");
+ add(new StyledLabel(l[i],SwingConstants.CENTER,-3.0f),"growx");
}
add(new JLabel(),"push, sizegroup spacing");
JPanel panel = new JPanel(new MigLayout());
JCheckBox check = new JCheckBox("Do not ask me again");
panel.add(check,"wrap");
- panel.add(new ResizeLabel("You can change the default operation in the " +
+ panel.add(new StyledLabel("You can change the default operation in the " +
"preferences.",-2));
int sel = JOptionPane.showOptionDialog(null, // parent component
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
JPanel panel = new JPanel(new MigLayout());
JCheckBox dontAsk = new JCheckBox("Do not ask me again");
panel.add(dontAsk,"wrap");
- panel.add(new ResizeLabel("You can change the default operation in the " +
+ panel.add(new StyledLabel("You can change the default operation in the " +
"preferences.",-2));
int ret = JOptionPane.showConfirmDialog(
import net.sf.openrocket.document.events.SimulationChangeEvent;
import net.sf.openrocket.gui.adaptors.Column;
import net.sf.openrocket.gui.adaptors.ColumnTableModel;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
import net.sf.openrocket.simulation.FlightData;
JPanel panel = new JPanel(new MigLayout());
JCheckBox dontAsk = new JCheckBox("Do not ask me again");
panel.add(dontAsk,"wrap");
- panel.add(new ResizeLabel("You can change the default operation in the " +
+ panel.add(new StyledLabel("You can change the default operation in the " +
"preferences.",-2));
int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
// Initialize the label
if (label == null) {
- label = new ResizeLabel(2f);
+ label = new StyledLabel(2f);
label.setIconTextGap(1);
// label.setFont(label.getFont().deriveFont(Font.BOLD));
}
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.ResizeLabel;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightEvent;
this.add(domainUnitSelector, "width 40lp, gapright para");
- ResizeLabel desc = new ResizeLabel("<html><p>The data will be plotted in time order " +
+ StyledLabel desc = new StyledLabel("<html><p>The data will be plotted in time order " +
"even if the X axis type is not time.", -2);
this.add(desc, "width :0px:, growx, wrap para");
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A filtering InputStream that limits the number of bytes that can be
+ * read from a stream. This can be used to enforce security, so that overlong
+ * input is ignored.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class LimitedInputStream extends FilterInputStream {
+
+ private int remaining;
+
+ public LimitedInputStream(InputStream is, int limit) {
+ super(is);
+ this.remaining = limit;
+ }
+
+
+ @Override
+ public int available() throws IOException {
+ int available = super.available();
+ return Math.min(available, remaining);
+ }
+
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (remaining <= 0)
+ return -1;
+
+ int result = super.read(b, off, Math.min(len, remaining));
+ if (result >= 0)
+ remaining -= result;
+ return result;
+ }
+
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n > remaining)
+ n = remaining;
+ long result = super.skip(n);
+ remaining -= result;
+ return result;
+ }
+
+
+ @Override
+ public int read() throws IOException {
+ if (remaining <= 0)
+ return -1;
+
+ int result = super.read();
+ if (result >= 0)
+ remaining--;
+ return result;
+ }
+
+
+
+ // Disable mark support
+
+ @Override
+ public void mark(int readlimit) {
+
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("mark/reset not supported");
+ }
+
+}
private static final String BUILD_VERSION;
private static final String BUILD_SOURCE;
public static final String DEFAULT_BUILD_SOURCE = "default";
+ private static final boolean DEFAULT_CHECK_UPDATES;
static {
try {
BUILD_SOURCE = props.getProperty("build.source");
+ String value = props.getProperty("build.checkupdates");
+ if (value != null)
+ DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
+ else
+ DEFAULT_CHECK_UPDATES = true;
+
} catch (IOException e) {
throw new MissingResourceException(
"Error reading build.properties",
public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
+ private static final String CHECK_UPDATES = "CheckUpdates";
+
/**
* Node to this application's preferences.
* @deprecated Use the static methods instead.
}
- private static final Material DEFAULT_LINE_MATERIAL =
- Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
- 0.0018, false);
- private static final Material DEFAULT_SURFACE_MATERIAL =
- Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
- private static final Material DEFAULT_BULK_MATERIAL =
- Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
-
+ /*
+ * Within a holder class so they will load only when needed.
+ */
+ private static class DefaultMaterialHolder {
+ private static final Material DEFAULT_LINE_MATERIAL =
+ Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
+ 0.0018, false);
+ private static final Material DEFAULT_SURFACE_MATERIAL =
+ Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
+ private static final Material DEFAULT_BULK_MATERIAL =
+ Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
+ }
//////////////////////
+ public static boolean getCheckUpdates() {
+ return PREFNODE.getBoolean(CHECK_UPDATES, DEFAULT_CHECK_UPDATES);
+ }
+
+ public static void setCheckUpdates(boolean check) {
+ PREFNODE.putBoolean(CHECK_UPDATES, check);
+ storeVersion();
+ }
+
+
//////////////////
public static File getDefaultDirectory() {
switch (type) {
case LINE:
- return DEFAULT_LINE_MATERIAL;
+ return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
case SURFACE:
- return DEFAULT_SURFACE_MATERIAL;
+ return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
case BULK:
- return DEFAULT_BULK_MATERIAL;
+ return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
}
throw new IllegalArgumentException("Unknown material type: "+type);
}
public class MotorCompare {
+ /** Maximum allowed difference in maximum thrust */
private static final double MAX_THRUST_MARGIN = 0.20;
+ /** Maximum allowed difference in total impulse */
private static final double TOTAL_IMPULSE_MARGIN = 0.10;
+ /** Maximum allowed difference in mass values */
private static final double MASS_MARGIN = 0.10;
-
- private static final double THRUST_MARGIN = 0.15;
-
+
+ /** Number of time points in thrust curve to compare */
private static final int DIVISIONS = 100;
+ /** Maximum difference in thrust for a time point to be considered invalid */
+ private static final double THRUST_MARGIN = 0.15;
+ /** Number of invalid time points allowed */
private static final int ALLOWED_INVALID_POINTS = 15;
-
+
+ /** Minimum number of thrust curve points allowed (incl. start and end points) */
private static final int MIN_POINTS = 7;
+
public static void main(String[] args) throws IOException {
final double maxThrust;
final double maxTime;
--- /dev/null
+package net.sf.openrocket.communication;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import net.sf.openrocket.util.Prefs;
+
+import org.junit.Test;
+
+
+public class BugReportTest {
+
+ private HttpURLConnectionMock setup() {
+ HttpURLConnectionMock connection = new HttpURLConnectionMock();
+ Communicator.setConnectionSource(new ConnectionSourceStub(connection));
+
+ connection.setUseCaches(true);
+ return connection;
+ }
+
+ private void check(HttpURLConnectionMock connection) {
+ assertEquals(Communicator.BUG_REPORT_URL, connection.getTrueUrl());
+ assertTrue(connection.getConnectTimeout() > 0);
+ assertEquals(Prefs.getVersion(), connection.getRequestProperty("X-OpenRocket-Version"));
+ assertTrue(connection.getInstanceFollowRedirects());
+ assertEquals("POST", connection.getRequestMethod());
+ assertFalse(connection.getUseCaches());
+ }
+
+
+ @Test
+ public void testBugReportSuccess() throws IOException {
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(Communicator.BUG_REPORT_RESPONSE_CODE);
+
+ String message =
+ "MyMessage\n"+
+ "is important\n"+
+ "h\u00e4h?";
+
+ BugReporter.sendBugReport(message);
+
+ check(connection);
+
+ String msg = connection.getOutputStreamString();
+ assertTrue(msg.indexOf("version=" + Prefs.getVersion()) >= 0);
+ assertTrue(msg.indexOf(Communicator.encode(message)) >= 0);
+ }
+
+
+ @Test
+ public void testBugReportFailure() throws IOException {
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(200);
+
+ String message =
+ "MyMessage\n"+
+ "is important\n"+
+ "h\u00e4h?";
+
+ try {
+ BugReporter.sendBugReport(message);
+ fail("Exception did not occur");
+ } catch (IOException e) {
+ // Success
+ }
+
+ check(connection);
+ }
+
+}
+++ /dev/null
-package net.sf.openrocket.communication;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.Random;
-
-import org.junit.Test;
-
-public class CommunicationTest {
-
- @Test
- public void testIllegalInputUpdateParsing() throws IOException {
-
- UpdateInfo info;
-
- info = Communication.parseUpdateInput(new StringReader(""));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("vj\u00e4avdsads"));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2"));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2pre"));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2.x"));
- assertNull(info);
-
- info = Communication.parseUpdateInput(new StringReader("\u0000\u0001\u0002"));
- assertNull(info);
-
- // Feed random bad input
- Random rnd = new Random();
- StringBuilder sb = new StringBuilder(10000);
- for (int i=0; i<100; i++) {
- int length = rnd.nextInt(10000);
- sb.delete(0, sb.length());
- for (int j=0; j<length; j++) {
- sb.append((char)rnd.nextInt());
- }
- info = Communication.parseUpdateInput(new StringReader(sb.toString()));
- assertNull(info);
- }
-
- }
-
-
-
- @Test
- public void testValidInputUpdateParsing() throws IOException {
-
- UpdateInfo info;
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3"));
- assertNotNull(info);
- assertEquals("1.2.3", info.getLatestVersion());
- assertEquals(0, info.getUpdates().size());
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3pre"));
- assertNotNull(info);
- assertEquals("1.2.3pre", info.getLatestVersion());
- assertEquals(0, info.getUpdates().size());
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3-build-3"));
- assertNotNull(info);
- assertEquals("1.2.3-build-3", info.getLatestVersion());
- assertEquals(0, info.getUpdates().size());
-
- info = Communication.parseUpdateInput(new StringReader("Version: 1.2.3x\n\n"));
- assertNotNull(info);
- assertEquals("1.2.3x", info.getLatestVersion());
- assertEquals(0, info.getUpdates().size());
-
- info = Communication.parseUpdateInput(new StringReader("Version:1.2.3\nfdsacd\u00e4fdsa"));
- assertNotNull(info);
- assertEquals("1.2.3", info.getLatestVersion());
- assertEquals(0, info.getUpdates().size());
-
- info = Communication.parseUpdateInput(new StringReader(
- "Version: 1.2.3 \n" +
- "15: Fifteen\n" +
- "3: Three1 \r\n" +
- "3: Three2\r" +
- "1:One"));
- assertNotNull(info);
- assertEquals("1.2.3", info.getLatestVersion());
- assertEquals(4, info.getUpdates().size());
- assertEquals(15, info.getUpdates().get(0).getU());
- assertEquals(3, info.getUpdates().get(1).getU());
- assertEquals(3, info.getUpdates().get(2).getU());
- assertEquals(1, info.getUpdates().get(3).getU());
- assertEquals("Fifteen", info.getUpdates().get(0).getV());
- assertEquals("Three1", info.getUpdates().get(1).getV());
- assertEquals("Three2", info.getUpdates().get(2).getV());
- assertEquals("One", info.getUpdates().get(3).getV());
-
-
- info = Communication.parseUpdateInput(new StringReader(
- "Version: 1.2.3\n" +
- "15: (C) 1234 A&B %23 \\o/ \r\r\n" +
- "5: m\u00e4c\n" +
- "3: Invalid\u0000value\n" +
- "1: One\u0019two"));
- assertNotNull(info);
- assertEquals("1.2.3", info.getLatestVersion());
- assertEquals(1, info.getUpdates().size());
- assertEquals(15, info.getUpdates().get(0).getU());
- assertEquals("(C) 1234 A&B %23 \\o/", info.getUpdates().get(0).getV());
-
-
-
- }
-}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+public class ConnectionSourceStub implements ConnectionSource {
+
+ private final HttpURLConnection connection;
+
+ public ConnectionSourceStub(HttpURLConnection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public HttpURLConnection getConnection(String url) throws IOException {
+ if (connection instanceof HttpURLConnectionMock) {
+ ((HttpURLConnectionMock)connection).setTrueUrl(url);
+ }
+ return connection;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.security.Permission;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HttpURLConnectionMock extends HttpURLConnection {
+
+ private static final URL MOCK_URL;
+ static {
+ try {
+ MOCK_URL = new URL("http://localhost/");
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private volatile boolean instanceFollowRedirects = false;
+ private volatile String requestMethod = "";
+ private volatile int responseCode;
+ private Map<String, String> requestProperties = new HashMap<String, String>();
+ private volatile int connectTimeout = -1;
+ private volatile String contentEncoding = "";
+
+ private volatile boolean doInput = false;
+ private volatile boolean doOutput = false;
+
+ private volatile byte[] content = null;
+ private volatile String contentType = null;
+ private volatile boolean useCaches = false;
+
+
+ private volatile InputStream inputStream = null;
+ private volatile ByteArrayOutputStream outputStream = null;
+
+ private volatile String trueUrl = null;
+
+
+ private volatile boolean connected = false;
+ private volatile int connectionDelay = 0;
+
+ private volatile boolean failed = false;
+
+
+
+
+ public HttpURLConnectionMock() {
+ super(MOCK_URL);
+ }
+
+ public HttpURLConnectionMock(URL u) {
+ super(u);
+ }
+
+
+
+ public String getTrueUrl() {
+ return trueUrl;
+ }
+
+ public void setTrueUrl(String url) {
+ assertNull(this.trueUrl);
+ this.trueUrl = url;
+ }
+
+
+ public boolean hasFailed() {
+ return failed;
+ }
+
+
+ public void setConnectionDelay(int delay) {
+ this.connectionDelay = delay;
+ }
+
+
+
+ @Override
+ public void connect() {
+ if (!connected) {
+ try {
+ Thread.sleep(connectionDelay);
+ } catch (InterruptedException e) {
+ }
+ connected = true;
+ }
+ }
+
+ @Override
+ public void disconnect() {
+
+ }
+
+ @Override
+ public boolean usingProxy() {
+ return false;
+ }
+
+
+
+
+ @Override
+ public boolean getInstanceFollowRedirects() {
+ return this.instanceFollowRedirects;
+ }
+
+ @Override
+ public void setInstanceFollowRedirects(boolean followRedirects) {
+ assertFalse(connected);
+ this.instanceFollowRedirects = followRedirects;
+ }
+
+ @Override
+ public String getRequestMethod() {
+ return this.requestMethod;
+ }
+
+ @Override
+ public void setRequestMethod(String method) throws ProtocolException {
+ assertFalse(connected);
+ this.requestMethod = method;
+ }
+
+ @Override
+ public int getResponseCode() throws IOException {
+ connect();
+ return this.responseCode;
+ }
+
+ public void setResponseCode(int code) {
+ this.responseCode = code;
+ }
+
+
+ @Override
+ public void addRequestProperty(String key, String value) {
+ assertFalse(connected);
+ assertFalse(this.requestProperties.containsKey(key.toLowerCase()));
+ this.requestProperties.put(key.toLowerCase(), value);
+ }
+
+
+ @Override
+ public void setRequestProperty(String key, String value) {
+ assertFalse(connected);
+ this.requestProperties.put(key.toLowerCase(), value);
+ }
+
+
+ @Override
+ public String getRequestProperty(String key) {
+ return this.requestProperties.get(key.toLowerCase());
+ }
+
+
+ @Override
+ public int getConnectTimeout() {
+ return this.connectTimeout;
+ }
+
+ @Override
+ public void setConnectTimeout(int timeout) {
+ assertFalse(connected);
+ this.connectTimeout = timeout;
+ }
+
+
+
+ @Override
+ public String getContentEncoding() {
+ connect();
+ return this.contentEncoding;
+ }
+
+ public void setContentEncoding(String encoding) {
+ this.contentEncoding = encoding;
+ }
+
+
+
+ @Override
+ public int getContentLength() {
+ connect();
+ if (content == null)
+ return 0;
+ return content.length;
+ }
+
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ public void setContent(String content) {
+ try {
+ this.content = content.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ fail("UTF-8");
+ }
+ }
+
+
+ @Override
+ public String getContentType() {
+ connect();
+ return this.contentType;
+ }
+
+ public void setContentType(String type) {
+ this.contentType = type;
+ }
+
+
+
+ @Override
+ public boolean getDoInput() {
+ return this.doInput;
+ }
+
+
+ @Override
+ public void setDoInput(boolean doinput) {
+ assertFalse(connected);
+ this.doInput = doinput;
+ }
+
+
+ @Override
+ public boolean getDoOutput() {
+ return this.doOutput;
+ }
+
+
+ @Override
+ public void setDoOutput(boolean dooutput) {
+ assertFalse(connected);
+ this.doOutput = dooutput;
+ }
+
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ assertTrue(doInput);
+ assertNull(inputStream);
+ assertNotNull(content);
+
+ connect();
+ inputStream = new ByteArrayInputStream(content);
+ return inputStream;
+ }
+
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ assertTrue(doOutput);
+ assertNull(outputStream);
+ outputStream = new ByteArrayOutputStream();
+ return outputStream;
+ }
+
+ public byte[] getOutputStreamData() {
+ return outputStream.toByteArray();
+ }
+
+ public String getOutputStreamString() {
+ try {
+ return outputStream.toString("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ fail("UTF-8");
+ return null;
+ }
+ }
+
+
+
+ @Override
+ public void setUseCaches(boolean usecaches) {
+ assertFalse(connected);
+ this.useCaches = usecaches;
+ }
+
+
+
+ @Override
+ public boolean getUseCaches() {
+ return this.useCaches;
+ }
+
+
+
+
+
+
+
+
+ private void assertNull(Object o) {
+ try {
+ org.junit.Assert.assertNull(o);
+ } catch (AssertionError e) {
+ failed = true;
+ throw e;
+ }
+ }
+
+ private void assertNotNull(Object o) {
+ try {
+ org.junit.Assert.assertNotNull(o);
+ } catch (AssertionError e) {
+ failed = true;
+ throw e;
+ }
+ }
+
+ private void assertTrue(boolean o) {
+ try {
+ org.junit.Assert.assertTrue(o);
+ } catch (AssertionError e) {
+ failed = true;
+ throw e;
+ }
+ }
+
+ private void assertFalse(boolean o) {
+ try {
+ org.junit.Assert.assertFalse(o);
+ } catch (AssertionError e) {
+ failed = true;
+ throw e;
+ }
+ }
+
+ private void fail(String msg) {
+ failed = true;
+ org.junit.Assert.fail(msg);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @Override
+ public InputStream getErrorStream() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public String getHeaderField(int n) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public long getHeaderFieldDate(String name, long Default) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public String getHeaderFieldKey(int n) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public Permission getPermission() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public String getResponseMessage() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public void setChunkedStreamingMode(int chunklen) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public void setFixedLengthStreamingMode(int contentLength) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+
+
+ @Override
+ public boolean getAllowUserInteraction() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public Object getContent() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getContent(Class[] classes) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public long getDate() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public boolean getDefaultUseCaches() {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public long getExpiration() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public String getHeaderField(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public int getHeaderFieldInt(String name, int Default) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public Map<String, List<String>> getHeaderFields() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public long getIfModifiedSince() {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public long getLastModified() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getReadTimeout() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public Map<String, List<String>> getRequestProperties() {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public URL getURL() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public void setAllowUserInteraction(boolean allowuserinteraction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDefaultUseCaches(boolean defaultusecaches) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public void setIfModifiedSince(long ifmodifiedsince) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public void setReadTimeout(int timeout) {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+
+
+ @Override
+ public String toString() {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+
+}
--- /dev/null
+package net.sf.openrocket.communication;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import net.sf.openrocket.util.ComparablePair;
+import net.sf.openrocket.util.Prefs;
+
+import org.junit.Test;
+
+public class UpdateInfoTest {
+
+ /** The connection delay */
+ private static final int DELAY = 100;
+
+ /** How much long does the test allow it to take */
+ private static final int ALLOWANCE = 2000;
+
+
+ private HttpURLConnectionMock setup() {
+ HttpURLConnectionMock connection = new HttpURLConnectionMock();
+ Communicator.setConnectionSource(new ConnectionSourceStub(connection));
+
+ connection.setConnectionDelay(DELAY);
+ connection.setUseCaches(true);
+ connection.setContentType("text/plain");
+ return connection;
+ }
+
+ private void check(HttpURLConnectionMock connection) {
+ assertEquals(Communicator.UPDATE_INFO_URL + "?version=" + Prefs.getVersion(),
+ connection.getTrueUrl());
+ assertTrue(connection.getConnectTimeout() > 0);
+ assertEquals(Prefs.getVersion(), connection.getRequestProperty("X-OpenRocket-Version"));
+ assertNotNull(connection.getRequestProperty("X-OpenRocket-Country"));
+ assertNotNull(connection.getRequestProperty("X-OpenRocket-ID"));
+ assertNotNull(connection.getRequestProperty("X-OpenRocket-OS"));
+ assertNotNull(connection.getRequestProperty("X-OpenRocket-Java"));
+ assertTrue(connection.getInstanceFollowRedirects());
+ assertEquals("GET", connection.getRequestMethod());
+ assertFalse(connection.getUseCaches());
+ }
+
+
+ @Test
+ public void testUpdateAvailable() throws IOException {
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+
+ String content =
+ "Version: 6.6.6pre A \n" +
+ "Extra: information\n" +
+ "100:hundred\n" +
+ "50: m\u00e4 \n\n" +
+ "1: one\n" +
+ "-2: none";
+ connection.setContent(content);
+
+ UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+ retriever.start();
+
+ // Info is null while processing
+ assertNull(retriever.getUpdateInfo());
+
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+
+ UpdateInfo info = retriever.getUpdateInfo();
+ assertNotNull(info);
+
+ check(connection);
+
+ assertEquals("6.6.6pre A", info.getLatestVersion());
+
+ List<ComparablePair<Integer, String>> updates = info.getUpdates();
+ assertEquals(3, updates.size());
+ Collections.sort(updates);
+ assertEquals(1, (int)updates.get(0).getU());
+ assertEquals("one", updates.get(0).getV());
+ assertEquals(50, (int)updates.get(1).getU());
+ assertEquals("m\u00e4", updates.get(1).getV());
+ assertEquals(100, (int)updates.get(2).getU());
+ assertEquals("hundred", updates.get(2).getV());
+ }
+
+
+
+
+ @Test
+ public void testUpdateNotAvailable() throws IOException {
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_NO_UPDATE_CODE);
+
+ String content =
+ "Version: 6.6.6pre A \n" +
+ "Extra: information\n" +
+ "100:hundred\n" +
+ "50: m\u00e4 \n\n" +
+ "1: one\n" +
+ "-2: none";
+ connection.setContent(content);
+
+ UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+ retriever.start();
+
+ // Info is null while processing
+ assertNull(retriever.getUpdateInfo());
+
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+
+ UpdateInfo info = retriever.getUpdateInfo();
+ assertNotNull(info);
+
+ check(connection);
+
+ assertEquals(Prefs.getVersion(), info.getLatestVersion());
+ assertEquals(0, info.getUpdates().size());
+ }
+
+
+
+ @Test
+ public void testInvalidResponses() {
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(404);
+ connection.setContent("Version: 1.2.3");
+
+ UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+ retriever.start();
+ assertNull(retriever.getUpdateInfo());
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+ assertNull(retriever.getUpdateInfo());
+ check(connection);
+
+
+ connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+ connection.setContentType("text/xml");
+
+ retriever = new UpdateInfoRetriever();
+ retriever.start();
+ assertNull(retriever.getUpdateInfo());
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+ assertNull(retriever.getUpdateInfo());
+ check(connection);
+
+
+
+ connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+ String content =
+ "100:hundred\n" +
+ "50: m\u00e4 \n\n" +
+ "1: one\n";
+ connection.setContent(content);
+
+ retriever = new UpdateInfoRetriever();
+ retriever.start();
+ assertNull(retriever.getUpdateInfo());
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+ assertNull(retriever.getUpdateInfo());
+ check(connection);
+
+
+ connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+ connection.setContent(new byte[0]);
+
+ retriever = new UpdateInfoRetriever();
+ retriever.start();
+ assertNull(retriever.getUpdateInfo());
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+ assertNull(retriever.getUpdateInfo());
+ check(connection);
+
+ }
+
+ @Test
+ public void testRandomInputData() {
+
+ Random rnd = new Random();
+ for (int i=0; i<10; i++) {
+ int size = (int) ((1 + 0.3 * rnd.nextGaussian()) * Math.pow(i, 6));
+ byte[] buf = new byte[size];
+ rnd.nextBytes(buf);
+
+ HttpURLConnectionMock connection = setup();
+ connection.setResponseCode(Communicator.UPDATE_INFO_UPDATE_AVAILABLE);
+ connection.setContent(buf);
+
+ UpdateInfoRetriever retriever = new UpdateInfoRetriever();
+ retriever.start();
+ assertNull(retriever.getUpdateInfo());
+ waitfor(retriever);
+ assertFalse(connection.hasFailed());
+ assertNull(retriever.getUpdateInfo());
+ check(connection);
+ }
+
+ }
+
+
+
+ private void waitfor(UpdateInfoRetriever retriever) {
+ long t = System.currentTimeMillis();
+
+ while (retriever.isRunning()) {
+ if (System.currentTimeMillis() >= t+ALLOWANCE) {
+ fail("retriever took too long to respond");
+ }
+
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) { }
+ }
+
+ System.out.println("Waiting took " + (System.currentTimeMillis()-t) + " ms");
+ }
+
+}
@Test
public void testComponentEquality() {
+
+ System.out.println("TEST CLASSPATH: " + System.getProperty("java.class.path"));
+
Rocket r1 = net.sf.openrocket.util.TestRockets.makeBigBlue();
Rocket r2 = net.sf.openrocket.util.TestRockets.makeBigBlue();