Start building MicroPeak GUI tool
authorKeith Packard <keithp@keithp.com>
Tue, 25 Dec 2012 22:23:29 +0000 (14:23 -0800)
committerKeith Packard <keithp@keithp.com>
Tue, 25 Dec 2012 22:23:29 +0000 (14:23 -0800)
Download, save and analyze MicroPeak flight data

Signed-off-by: Keith Packard <keithp@keithp.com>
micropeak/Makefile.am [new file with mode: 0644]
micropeak/MicroData.java [new file with mode: 0644]
micropeak/MicroGraph.java [new file with mode: 0644]
micropeak/MicroPeak.java [new file with mode: 0644]
micropeak/MicroSerial.java [new file with mode: 0644]
micropeak/MicroUSB.java [new file with mode: 0644]

diff --git a/micropeak/Makefile.am b/micropeak/Makefile.am
new file mode 100644 (file)
index 0000000..a3ecac7
--- /dev/null
@@ -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 (file)
index 0000000..783ae40
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package 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<Integer>      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<Integer>();
+               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 (file)
index 0000000..aac14b9
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package 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 (file)
index 0000000..82d926f
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package 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<MicroUSB>        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 (file)
index 0000000..afe5553
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2012 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package 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 (file)
index 0000000..d48610f
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package 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<MicroUSB> list() {
+               if (!load_library())
+                       return null;
+
+               SWIGTYPE_p_altos_list list = libaltos.altos_list_start();
+
+               ArrayList<MicroUSB> device_list = new ArrayList<MicroUSB>();
+               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