moving to core/
[debian/openrocket] / core / src-extra / altimeter / Alt15K.java
diff --git a/core/src-extra/altimeter/Alt15K.java b/core/src-extra/altimeter/Alt15K.java
new file mode 100644 (file)
index 0000000..0fba135
--- /dev/null
@@ -0,0 +1,562 @@
+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;
+                       
+       }
+       
+}