2 * Copyright © 2012 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 package org.altusmetrum.micropeak;
24 import org.altusmetrum.altoslib_11.*;
25 import org.altusmetrum.altosuilib_11.*;
27 class MicroIterator implements Iterator<MicroDataPoint> {
31 public boolean hasNext() {
32 return i < data.pressures.length;
35 public MicroDataPoint next() {
36 return new MicroDataPoint(data, i++);
39 public MicroIterator (MicroData data) {
44 public void remove() {
48 class MicroIterable implements Iterable<MicroDataPoint> {
52 public Iterator<MicroDataPoint> iterator() {
53 return new MicroIterator(data);
56 public MicroIterable(MicroData data) {
61 class MicroUIIterator implements Iterator<AltosUIDataPoint> {
65 public boolean hasNext() {
66 return i < data.pressures.length;
69 public AltosUIDataPoint next() {
70 return new MicroDataPoint(data, i++);
73 public MicroUIIterator (MicroData data) {
78 public void remove() {
82 class MicroUIIterable implements Iterable<AltosUIDataPoint> {
85 public Iterator<AltosUIDataPoint> iterator() {
86 return new MicroUIIterator(data);
89 public MicroUIIterable(MicroData data) {
94 public class MicroData implements AltosUIDataSet {
95 public int ground_pressure;
96 public int min_pressure;
97 public int[] pressures;
98 private double time_step;
99 private double ground_altitude;
100 private ArrayList<Integer> bytes;
105 public static final int LOG_ID_MICROPEAK = 0;
106 public static final int LOG_ID_MICROKITE = 1;
107 public static final int LOG_ID_MICROPEAK2 = 2;
109 public static final double CLOCK_MP1 = 0.096;
110 public static final double CLOCK_MP2 = 0.1;
112 public class FileEndedException extends Exception {
115 public class NonHexcharException extends Exception {
118 public class InvalidCrcException extends Exception {
121 private int getc(InputStream f) throws IOException, FileEndedException {
125 throw new FileEndedException();
130 private int get_nonwhite(InputStream f) throws IOException, FileEndedException {
135 if (!Character.isWhitespace(c))
140 private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException {
141 int c = get_nonwhite(f);
143 if ('0' <= c && c <= '9')
145 if ('a' <= c && c <= 'f')
147 if ('A' <= c && c <= 'F')
149 throw new NonHexcharException();
152 private static final int POLY = 0x8408;
154 private int log_crc(int crc, int b) {
157 for (i = 0; i < 8; i++) {
158 if (((crc & 0x0001) ^ (b & 0x0001)) != 0)
159 crc = (crc >> 1) ^ POLY;
169 private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException {
173 int h = (a << 4) + b;
175 file_crc = log_crc(file_crc, h);
179 private boolean find_header(InputStream f) throws IOException, FileEndedException {
181 if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P')
186 private int get_32(InputStream f) throws IOException, FileEndedException, NonHexcharException {
188 for (int i = 0; i < 4; i++) {
189 v += get_hex(f) << (i * 8);
194 private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException {
196 for (int i = 0; i < 2; i++) {
197 v += get_hex(f) << (i * 8);
202 private int swap16(int i) {
203 return ((i << 8) & 0xff00) | ((i >> 8) & 0xff);
206 public boolean crc_valid;
208 int mix_in (int high, int low) {
209 return high - (high & 0xffff) + low;
212 boolean closer (int target, int a, int b) {
213 return Math.abs (target - a) < Math.abs(target - b);
216 public double altitude(int i) {
217 return AltosConvert.pressure_to_altitude(pressures[i]);
220 public String name() {
224 public Iterable<AltosUIDataPoint> dataPoints() {
225 return new MicroUIIterable(this);
228 public Iterable<MicroDataPoint> points() {
229 return new MicroIterable(this);
235 return n * fact(n-1);
238 int choose(int n, int k) {
239 return fact(n) / (fact(k) * fact(n-k));
243 public double avg_altitude(int center, int dist) {
244 int start = center - dist;
245 int stop = center + dist;
249 if (stop >= pressures.length)
250 stop = pressures.length - 1;
257 for (int i = start; i <= stop; i++) {
258 int k = i - (center - dist);
259 int c = choose (n, k);
261 sum += c * pressures[i];
265 double pres = sum / div;
267 double alt = AltosConvert.pressure_to_altitude(pres);
271 public double pressure(int i) {
275 public double height(int i) {
276 return altitude(i) - ground_altitude;
279 public double apogee_pressure() {
283 public double apogee_altitude() {
284 return AltosConvert.pressure_to_altitude(apogee_pressure());
287 public double apogee_height() {
288 return apogee_altitude() - ground_altitude;
291 static final int speed_avg = 3;
292 static final int accel_avg = 5;
294 private double avg_speed(int center, int dist) {
298 double ai = avg_altitude(center, dist);
299 double aj = avg_altitude(center - 1, dist);
300 double s = (ai - aj) / time_step;
305 public double speed(int i) {
306 return avg_speed(i, speed_avg);
309 public double acceleration(int i) {
312 return (avg_speed(i, accel_avg) - avg_speed(i-1, accel_avg)) / time_step;
315 public double time(int i) {
316 return i * time_step;
319 public void save (OutputStream f) throws IOException {
325 public void export (Writer f) throws IOException {
326 PrintWriter pw = new PrintWriter(f);
327 pw.printf(" Time, Press(Pa), Height(m), Height(f), Speed(m/s), Speed(mph), Speed(mach), Accel(m/s²), Accel(ft/s²), Accel(g)\n");
328 for (MicroDataPoint point : points()) {
329 pw.printf("%6.3f,%10.0f,%10.1f,%10.1f,%11.2f,%11.2f,%12.4f,%12.2f,%13.2f,%10.4f\n",
333 AltosConvert.meters_to_feet(point.height),
335 AltosConvert.meters_to_mph(point.speed),
336 AltosConvert.meters_to_mach(point.speed),
338 AltosConvert.meters_to_feet(point.accel),
339 AltosConvert.meters_to_g(point.accel));
343 public void set_name(String name) {
347 public MicroData (InputStream f, String name) throws IOException, InterruptedException, NonHexcharException, FileEndedException {
349 bytes = new ArrayList<Integer>();
351 throw new IOException("No MicroPeak data header found");
354 ground_pressure = get_32(f);
355 min_pressure = get_32(f);
356 int nsamples = get_16(f);
358 log_id = nsamples >> 12;
360 pressures = new int[nsamples + 1];
362 ground_altitude = AltosConvert.pressure_to_altitude(ground_pressure);
363 int cur = ground_pressure;
365 for (int i = 0; i < nsamples; i++) {
367 int same = mix_in(cur, k);
368 int up = mix_in(cur + 0x10000, k);
369 int down = mix_in(cur - 0x10000, k);
371 if (closer (cur, same, up)) {
372 if (closer (cur, same, down))
377 if (closer (cur, up, down))
383 pressures[i+1] = cur;
386 int current_crc = swap16(~file_crc & 0xffff);
389 crc_valid = crc == current_crc;
392 case LOG_ID_MICROPEAK:
393 time_step = 2 * CLOCK_MP1;
395 case LOG_ID_MICROKITE:
396 time_step = 200 * CLOCK_MP1;
398 case LOG_ID_MICROPEAK2:
399 time_step = CLOCK_MP2;
402 throw new IOException(String.format("Unknown device type: %d", log_id));
404 stats = new MicroStats(this);
405 } catch (FileEndedException fe) {
406 throw new IOException("File Ended Unexpectedly");
411 ground_pressure = 101000;
412 min_pressure = 101000;
413 pressures = new int[1];
414 pressures[0] = 101000;