563 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			563 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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;
 | |
| 			
 | |
| 	}
 | |
| 	
 | |
| }
 |