From bf8e1b6eecb2bae12ffdbd730bd6ec12ccdaf23a Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Tue, 25 Dec 2012 14:23:29 -0800 Subject: [PATCH] Start building MicroPeak GUI tool Download, save and analyze MicroPeak flight data Signed-off-by: Keith Packard --- micropeak/Makefile.am | 115 ++++++++++++++++ micropeak/MicroData.java | 270 +++++++++++++++++++++++++++++++++++++ micropeak/MicroGraph.java | 105 +++++++++++++++ micropeak/MicroPeak.java | 138 +++++++++++++++++++ micropeak/MicroSerial.java | 46 +++++++ micropeak/MicroUSB.java | 103 ++++++++++++++ 6 files changed, 777 insertions(+) create mode 100644 micropeak/Makefile.am create mode 100644 micropeak/MicroData.java create mode 100644 micropeak/MicroGraph.java create mode 100644 micropeak/MicroPeak.java create mode 100644 micropeak/MicroSerial.java create mode 100644 micropeak/MicroUSB.java diff --git a/micropeak/Makefile.am b/micropeak/Makefile.am new file mode 100644 index 00000000..a3ecac72 --- /dev/null +++ b/micropeak/Makefile.am @@ -0,0 +1,115 @@ +JAVAROOT=classes +AM_JAVACFLAGS=-encoding UTF-8 -Xlint:deprecation + +CLASSPATH_ENV=mkdir -p $(JAVAROOT); CLASSPATH=".:classes:../altoslib/*:../libaltos:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar" + +bin_SCRIPTS=micropeak + +micropeakdir=$(datadir)/java + +micropeak_JAVA= \ + MicroPeak.java \ + MicroData.java \ + MicroGraph.java \ + MicroSerial.java \ + MicroUSB.java + +JFREECHART_CLASS= \ + jfreechart.jar + +JCOMMON_CLASS=\ + jcommon.jar + +JAR=micropeak.jar + +FATJAR=micropeak-fat.jar + +LIBALTOS= \ + libaltos.so \ + libaltos.dylib \ + altos.dll + +ALTOSLIB_CLASS=\ + AltosLib.jar + +all-local: micropeak-test $(JAR) + +clean-local: + -rm -rf classes $(JAR) $(FATJAR) \ + $(ALTOSLIB_CLASS) \ + $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(LIBALTOS) Manifest.txt \ + micropeak micropeak-test macosx linux windows + +micropeak: Makefile + echo "#!/bin/sh" > $@ + echo 'exec java -cp "$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar" -Djava.library.path="$(altoslibdir)" -jar "$(micropeakdir)/micropeak.jar" "$$@"' >> $@ + chmod +x $@ + +micropeak-test: Makefile + echo "#!/bin/sh" > $@ + echo 'exec java -cp "./*:../libaltos/*:$(JCOMMON)/jcommon.jar:$(JFREECHART)/jfreechart.jar" -Djava.library.path="../libaltos/.libs" -jar micropeak.jar "$$@"' >> $@ + chmod +x $@ + +$(JAR): classmicropeak.stamp Manifest.txt $(JAVA_ICONS) $(ALTOSLIB_CLASS) + jar cfm $@ Manifest.txt \ + $(ICONJAR) \ + -C classes org \ + -C ../libaltos libaltosJNI + +$(FATJAR): classmicropeak.stamp Manifest-fat.txt $(ALTOSLIB_CLASS) $(JFREECHART_CLASS) $(JCOMMON_CLASS) $(JAVA_ICONS) + jar cfm $@ Manifest-fat.txt \ + $(ICONJAR) \ + -C classes org \ + -C ../libaltos libaltosJNI + + +libaltos.so: build-libaltos + -rm -f "$@" + $(LN_S) ../libaltos/.libs/"$@" . + +libaltos.dylib: + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +altos.dll: ../libaltos/altos.dll + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +altos64.dll: ../libaltos/altos64.dll + -rm -f "$@" + $(LN_S) ../libaltos/"$@" . + +../libaltos/.libs/libaltos.so: build-libaltos + +../libaltos/altos.dll: build-altos-dll + +../libaltos/altos64.dll: build-altos64-dll + +build-libaltos: + +cd ../libaltos && make libaltos.la +build-altos-dll: + +cd ../libaltos && make altos.dll + +build-altos64-dll: + +cd ../libaltos && make altos64.dll + +$(ALTOSLIB_CLASS): + -rm -f "$@" + $(LN_S) ../altoslib/"$@" . + +$(JFREECHART_CLASS): + -rm -f "$@" + $(LN_S) "$(JFREECHART)"/"$@" . + +$(JCOMMON_CLASS): + -rm -f "$@" + $(LN_S) "$(JCOMMON)"/"$@" . + +Manifest.txt: Makefile + echo 'Main-Class: org.altusmetrum.micropeak.MicroPeak' > $@ + echo "Class-Path: AltosLib.jar $(JCOMMON)/jcommon.jar $(JFREECHART)/jfreechart.jar" >> $@ + +Manifest-fat.txt: + echo 'Main-Class: org.altusmetrum.micropeak.MicroPeak' > $@ + echo "Class-Path: AltosLib.jar jcommon.jar jfreechart.jar" >> $@ + diff --git a/micropeak/MicroData.java b/micropeak/MicroData.java new file mode 100644 index 00000000..783ae40f --- /dev/null +++ b/micropeak/MicroData.java @@ -0,0 +1,270 @@ +/* + * Copyright © 2012 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.micropeak; + +import java.lang.*; +import java.io.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + +public class MicroData { + public int ground_pressure; + public int min_pressure; + public int[] pressures; + private double time_step; + private double ground_altitude; + private ArrayList bytes; + + + class FileEndedException extends Exception { + } + + class NonHexcharException extends Exception { + } + + class InvalidCrcException extends Exception { + } + + private int getc(InputStream f) throws IOException, FileEndedException { + int c = f.read(); + + if (c == -1) + throw new FileEndedException(); + bytes.add(c); + return c; + } + + private int get_nonwhite(InputStream f) throws IOException, FileEndedException { + int c; + + for (;;) { + c = getc(f); + if (!Character.isWhitespace(c)) + return c; + } + } + + private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException { + int c = get_nonwhite(f); + + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + throw new NonHexcharException(); + } + + private static final int POLY = 0x8408; + + private int log_crc(int crc, int b) { + int i; + + for (i = 0; i < 8; i++) { + if (((crc & 0x0001) ^ (b & 0x0001)) != 0) + crc = (crc >> 1) ^ POLY; + else + crc = crc >> 1; + b >>= 1; + } + return crc & 0xffff; + } + + int file_crc; + + private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException { + int a = get_hexc(f); + int b = get_hexc(f); + + int h = (a << 4) + b; + + file_crc = log_crc(file_crc, h); + return h; + } + + private boolean find_header(InputStream f) throws IOException { + try { + for (;;) { + if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P') + return true; + } + } catch (FileEndedException fe) { + return false; + } + } + + private int get_32(InputStream f) throws IOException, FileEndedException, NonHexcharException { + int v = 0; + for (int i = 0; i < 4; i++) { + v += get_hex(f) << (i * 8); + } + return v; + } + + private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException { + int v = 0; + for (int i = 0; i < 2; i++) { + v += get_hex(f) << (i * 8); + } + return v; + } + + private int swap16(int i) { + return ((i << 8) & 0xff00) | ((i >> 8) & 0xff); + } + + public boolean crc_valid; + + int mix_in (int high, int low) { + return high - (high & 0xffff) + low; + } + + boolean closer (int target, int a, int b) { + return Math.abs (target - a) < Math.abs(target - b); + } + + public double altitude(int i) { + return AltosConvert.pressure_to_altitude(pressures[i]); + } + + int fact(int n) { + if (n == 0) + return 1; + return n * fact(n-1); + } + + int choose(int n, int k) { + return fact(n) / (fact(k) * fact(n-k)); + } + + + public double avg_altitude(int center, int dist) { + int start = center - dist; + int stop = center + dist; + + if (start < 0) + start = 0; + if (stop >= pressures.length) + stop = pressures.length - 1; + + double sum = 0; + double div = 0; + + int n = dist * 2; + + for (int i = start; i <= stop; i++) { + int k = i - (center - dist); + int c = choose (n, k); + + sum += c * pressures[i]; + div += c; + } + + double pres = sum / div; + + double alt = AltosConvert.pressure_to_altitude(pres); + return alt; + } + + public double height(int i) { + return altitude(i) - ground_altitude; + } + + static final int speed_avg = 3; + static final int accel_avg = 5; + + private double avg_speed(int center, int dist) { + if (center == 0) + return 0; + + double ai = avg_altitude(center, dist); + double aj = avg_altitude(center - 1, dist); + double s = (ai - aj) / time_step; + + return s; + } + + public double speed(int i) { + return avg_speed(i, speed_avg); + } + + public double acceleration(int i) { + if (i == 0) + return 0; + return (avg_speed(i, accel_avg) - avg_speed(i-1, accel_avg)) / time_step; + } + + public double time(int i) { + return i * time_step; + } + + public void save (OutputStream f) throws IOException { + for (int c : bytes) + f.write(c); + } + + public MicroData (InputStream f) throws IOException { + bytes = new ArrayList(); + if (!find_header(f)) + throw new IOException(); + try { + file_crc = 0xffff; + ground_pressure = get_32(f); + min_pressure = get_32(f); + int nsamples = get_16(f); + pressures = new int[nsamples + 1]; + + ground_altitude = AltosConvert.pressure_to_altitude(ground_pressure); + int cur = ground_pressure; + pressures[0] = cur; + for (int i = 0; i < nsamples; i++) { + int k = get_16(f); + int same = mix_in(cur, k); + int up = mix_in(cur + 0x10000, k); + int down = mix_in(cur - 0x10000, k); + + if (closer (cur, same, up)) { + if (closer (cur, same, down)) + cur = same; + else + cur = down; + } else { + if (closer (cur, up, down)) + cur = up; + else + cur = down; + } + + pressures[i+1] = cur; + } + + int current_crc = swap16(~file_crc & 0xffff); + int crc = get_16(f); + + crc_valid = crc == current_crc; + + time_step = 0.192; + } catch (FileEndedException fe) { + throw new IOException(); + } catch (NonHexcharException ne) { + throw new IOException(); + } + } + +} diff --git a/micropeak/MicroGraph.java b/micropeak/MicroGraph.java new file mode 100644 index 00000000..aac14b9a --- /dev/null +++ b/micropeak/MicroGraph.java @@ -0,0 +1,105 @@ +/* + * Copyright © 2012 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.micropeak; + +import java.io.*; +import java.util.ArrayList; + +import java.awt.*; +import javax.swing.*; +import org.altusmetrum.AltosLib.*; + +import org.jfree.ui.*; +import org.jfree.chart.*; +import org.jfree.chart.plot.*; +import org.jfree.chart.axis.*; +import org.jfree.chart.renderer.*; +import org.jfree.chart.renderer.xy.*; +import org.jfree.chart.labels.*; +import org.jfree.data.xy.*; +import org.jfree.data.*; + +public class MicroGraph { + + XYPlot plot; + JFreeChart chart; + ChartPanel panel; + NumberAxis xAxis; + XYSeries heightSeries; + XYSeries speedSeries; + XYSeries accelSeries; + + MicroData data; + + public JPanel panel() { + return panel; + } + + private void addSeries(XYSeries series, int index, String label, String units) { + XYSeriesCollection dataset = new XYSeriesCollection(series); + NumberAxis axis = new NumberAxis(String.format("%s (%s)", label, units)); + XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false); + + renderer.setPlot(plot); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(String.format("{1}s: {2}%s ({0})", units), + new java.text.DecimalFormat("0.00"), + new java.text.DecimalFormat("0.00"))); + plot.setRangeAxis(index, axis); + plot.setDataset(index, dataset); + plot.setRenderer(index, renderer); + plot.mapDatasetToRangeAxis(index, index); + } + + public MicroGraph(MicroData data) { + + this.data = data; + + heightSeries = new XYSeries("Height"); + speedSeries = new XYSeries("Speed"); + accelSeries = new XYSeries("Acceleration"); + + for (int i = 0; i < data.pressures.length; i++) { + double x = data.time(i); + heightSeries.add(x, data.height(i)); + speedSeries.add(x, data.speed(i)); + accelSeries.add(x, data.acceleration(i)); + } + + xAxis = new NumberAxis("Time (s)"); + + xAxis.setAutoRangeIncludesZero(true); + + plot = new XYPlot(); + plot.setDomainAxis(xAxis); + plot.setOrientation(PlotOrientation.VERTICAL); + plot.setDomainPannable(true); + plot.setRangePannable(true); + + addSeries(heightSeries, 0, "Height", "m"); + addSeries(speedSeries, 1, "Speed", "m/s"); + addSeries(accelSeries, 2, "Acceleration", "m/s²"); + + chart = new JFreeChart("Flight", JFreeChart.DEFAULT_TITLE_FONT, + plot, true); + + ChartUtilities.applyCurrentTheme(chart); + panel = new ChartPanel(chart); + panel.setMouseWheelEnabled(true); + panel.setPreferredSize(new java.awt.Dimension(800, 500)); + } +} \ No newline at end of file diff --git a/micropeak/MicroPeak.java b/micropeak/MicroPeak.java new file mode 100644 index 00000000..82d926fb --- /dev/null +++ b/micropeak/MicroPeak.java @@ -0,0 +1,138 @@ +/* + * Copyright © 2012 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.micropeak; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.*; +import java.util.concurrent.*; +import java.util.*; +import org.altusmetrum.AltosLib.*; + +public class MicroPeak extends JFrame implements ActionListener, ItemListener { + + File filename; + MicroGraph graph; + MicroData data; + Container pane; + + private void OpenFile(File filename) { + try { + FileInputStream input = new FileInputStream(filename); + try { + data = new MicroData(input); + graph = new MicroGraph(data); + pane.add(graph.panel); + } catch (IOException ioe) { + } + try { + input.close(); + } catch (IOException ioe) { + } + } catch (FileNotFoundException fne) { + } + } + + private void SelectFile() { + } + + private void DownloadData() { + java.util.List devices = MicroUSB.list(); + for (MicroUSB device : devices) + System.out.printf("device %s\n", device.toString()); + } + + public void actionPerformed(ActionEvent ev) { + System.out.printf("action %s %s\n", ev.getActionCommand(), ev.paramString()); + if ("Exit".equals(ev.getActionCommand())) + System.exit(0); + else if ("Open".equals(ev.getActionCommand())) + SelectFile(); + else if ("New".equals(ev.getActionCommand())) + new MicroPeak(); + else if ("Download".equals(ev.getActionCommand())) + DownloadData(); + } + + public void itemStateChanged(ItemEvent e) { + } + + public MicroPeak(File filename) { + + this.filename = filename; + + pane = getContentPane(); + +// JLabel label = new JLabel ("Hello, World"); +// pane.add(label); + + setSize(800, 500); + + setTitle("MicroPeak"); + + JMenuBar menuBar = new JMenuBar(); + setJMenuBar(menuBar); + + JMenu fileMenu = new JMenu("File"); + menuBar.add(fileMenu); + + JMenuItem newAction = new JMenuItem("New"); + fileMenu.add(newAction); + newAction.addActionListener(this); + + JMenuItem openAction = new JMenuItem("Open"); + fileMenu.add(openAction); + openAction.addActionListener(this); + + JMenuItem downloadAction = new JMenuItem("Download"); + fileMenu.add(downloadAction); + downloadAction.addActionListener(this); + + JMenuItem exitAction = new JMenuItem("Exit"); + fileMenu.add(exitAction); + exitAction.addActionListener(this); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + System.exit(0); + } + }); + + if (filename != null) + this.OpenFile(filename); + setVisible(true); + } + + public MicroPeak() { + this(null); + } + + public static void main(final String[] args) { + boolean opened = false; + + for (int i = 0; i < args.length; i++) { + new MicroPeak(new File(args[i])); + opened = true; + } + if (!opened) + new MicroPeak(); + } +} \ No newline at end of file diff --git a/micropeak/MicroSerial.java b/micropeak/MicroSerial.java new file mode 100644 index 00000000..afe55532 --- /dev/null +++ b/micropeak/MicroSerial.java @@ -0,0 +1,46 @@ +/* + * Copyright © 2012 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.micropeak; +import java.util.*; +import java.io.*; +import libaltosJNI.*; + +public class MicroSerial extends InputStream { + SWIGTYPE_p_altos_file file; + + public int read() { + return libaltos.altos_getchar(file, 0); + } + + public void close() { + if (file != null) { + libaltos.altos_close(file); + file = null; + } + } + + public MicroSerial(MicroUSB usb) throws FileNotFoundException { + file = usb.open(); + if (file == null) { + final String message = usb.getErrorString(); + throw new FileNotFoundException(String.format("%s (%s)", + usb.toShortString(), + message)); + } + } +} diff --git a/micropeak/MicroUSB.java b/micropeak/MicroUSB.java new file mode 100644 index 00000000..d48610fe --- /dev/null +++ b/micropeak/MicroUSB.java @@ -0,0 +1,103 @@ +/* + * Copyright © 2010 Keith Packard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.micropeak; +import java.util.*; +import libaltosJNI.*; + +public class MicroUSB extends altos_device { + + static boolean initialized = false; + static boolean loaded_library = false; + + public static boolean load_library() { + if (!initialized) { + try { + System.loadLibrary("altos"); + libaltos.altos_init(); + loaded_library = true; + } catch (UnsatisfiedLinkError e) { + try { + System.loadLibrary("altos64"); + libaltos.altos_init(); + loaded_library = true; + } catch (UnsatisfiedLinkError e2) { + loaded_library = false; + } + } + initialized = true; + } + return loaded_library; + } + + public String toString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%-20.20s %4d %s", + name, getSerial(), getPath()); + } + + public String toShortString() { + String name = getName(); + if (name == null) + name = "Altus Metrum"; + return String.format("%s %d %s", + name, getSerial(), getPath()); + + } + + public String getErrorString() { + altos_error error = new altos_error(); + + libaltos.altos_get_last_error(error); + return String.format("%s (%d)", error.getString(), error.getCode()); + } + + public SWIGTYPE_p_altos_file open() { + return libaltos.altos_open(this); + } + + private boolean isMicro() { + if (getVendor() != 0x0403) + return false; + if (getProduct() != 0x6001) + return false; + return true; + } + + static java.util.List list() { + if (!load_library()) + return null; + + SWIGTYPE_p_altos_list list = libaltos.altos_list_start(); + + ArrayList device_list = new ArrayList(); + if (list != null) { + for (;;) { + MicroUSB device = new MicroUSB(); + if (libaltos.altos_list_next(list, device) == 0) + break; + if (device.isMicro()) + device_list.add(device); + } + libaltos.altos_list_finish(list); + } + + return device_list; + } +} \ No newline at end of file -- 2.30.2