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_14.*;
25 import org.altusmetrum.altosuilib_14.*;
27 public class MicroData {
28 public int ground_pressure;
29 public int min_pressure;
31 AltosUIFlightSeries flight_series;
32 AltosFlightStats flight_stats;
33 AltosCalData cal_data;
35 private double time_step;
36 private ArrayList<Integer> bytes;
42 public static final int LOG_ID_MICROPEAK = 0;
43 public static final int LOG_ID_MICROKITE = 1;
44 public static final int LOG_ID_MICROPEAK2 = 2;
46 public static final double CLOCK_MP1 = 0.096;
47 public static final double CLOCK_MP2 = 0.1;
49 public class FileEndedException extends Exception {
52 public class NonHexcharException extends Exception {
55 public class InvalidCrcException extends Exception {
58 private int getc(InputStream f) throws IOException, FileEndedException {
62 throw new FileEndedException();
67 private int get_nonwhite(InputStream f) throws IOException, FileEndedException {
72 if (!Character.isWhitespace(c))
77 private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException {
78 int c = get_nonwhite(f);
80 if ('0' <= c && c <= '9')
82 if ('a' <= c && c <= 'f')
84 if ('A' <= c && c <= 'F')
86 throw new NonHexcharException();
89 private static final int POLY = 0x8408;
91 private int log_crc(int crc, int b) {
94 for (i = 0; i < 8; i++) {
95 if (((crc & 0x0001) ^ (b & 0x0001)) != 0)
96 crc = (crc >> 1) ^ POLY;
106 private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException {
110 int h = (a << 4) + b;
112 file_crc = log_crc(file_crc, h);
116 private boolean find_header(InputStream f) throws IOException, FileEndedException {
118 if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P')
123 private int get_32(InputStream f) throws IOException, FileEndedException, NonHexcharException {
125 for (int i = 0; i < 4; i++) {
126 v += get_hex(f) << (i * 8);
131 private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException {
133 for (int i = 0; i < 2; i++) {
134 v += get_hex(f) << (i * 8);
139 private String get_line(InputStream f) throws IOException, FileEndedException, NonHexcharException {
141 StringBuffer line = new StringBuffer();
145 } while (Character.isWhitespace(c));
148 line.append((char) c);
150 } while (!Character.isWhitespace(c));
151 return new String(line);
154 private int swap16(int i) {
155 return ((i << 8) & 0xff00) | ((i >> 8) & 0xff);
158 public boolean crc_valid;
160 public boolean log_empty;
162 int mix_in (int high, int low) {
163 return high - (high & 0xffff) + low;
166 boolean closer (int target, int a, int b) {
167 return Math.abs (target - a) < Math.abs(target - b);
170 public double altitude(double time) {
171 if (flight_series.altitude_series == null)
173 return flight_series.altitude_series.value(time);
176 public double altitude(int i) {
177 return altitude(time(i));
180 public String name() {
184 public double pressure(int i) {
185 if (flight_series.pressure_series == null)
188 return flight_series.pressure_series.value(time(i));
191 public double height(double time) {
192 if (flight_series.height_series == null)
195 return flight_series.height_series.value(time);
198 public double height(int i) {
199 return height(time(i));
202 public int length() {
203 if (flight_series.pressure_series == null)
205 return flight_series.pressure_series.size();
208 /* Use the recorded apogee pressure for stats so that it agrees with the device */
209 public double apogee_pressure() {
213 public double apogee_altitude() {
214 return AltosConvert.pressure_to_altitude(apogee_pressure());
217 public double apogee_height() {
218 return apogee_altitude() - cal_data.ground_altitude;
221 public double speed(double time) {
222 if (flight_series.speed_series == null)
224 return flight_series.speed_series.value(time);
227 public double speed(int i) {
228 return speed(time(i));
231 public double acceleration(double time) {
232 if (flight_series.accel_series == null)
234 return flight_series.accel_series.value(time);
237 public double acceleration(int i) {
238 return acceleration(time(i));
241 public double time(int i) {
242 return i * time_step;
245 public void save (OutputStream f) throws IOException {
251 public boolean is_empty() {
252 boolean empty = true;
253 for (int c : bytes) {
254 if (!Character.isWhitespace(c) && c != 'f')
260 public void export (Writer f) throws IOException {
261 PrintWriter pw = new PrintWriter(f);
262 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");
264 for (AltosTimeValue ptv : flight_series.pressure_series) {
266 double height = height(ptv.time);
267 double speed = speed(ptv.time);
268 double accel = acceleration(ptv.time);
270 pw.printf("%6.3f,%10.0f,%10.1f,%10.1f,%11.2f,%11.2f,%12.4f,%12.2f,%13.2f,%10.4f\n",
274 AltosConvert.meters_to_feet(height),
276 AltosConvert.meters_to_mph(speed),
277 AltosConvert.meters_to_mach(speed),
279 AltosConvert.meters_to_feet(accel),
280 AltosConvert.meters_to_g(accel));
284 public void set_name(String name) {
289 ground_pressure = 101000;
290 min_pressure = 101000;
291 cal_data = new AltosCalData();
292 flight_series = new AltosUIFlightSeries(cal_data);
295 public MicroData (InputStream f, String name) throws IOException, InterruptedException, NonHexcharException, FileEndedException {
297 bytes = new ArrayList<Integer>();
299 cal_data = new AltosCalData();
300 flight_series = new AltosUIFlightSeries(cal_data);
303 throw new IOException("No MicroPeak data header found");
306 ground_pressure = get_32(f);
307 min_pressure = get_32(f);
308 nsamples = get_16(f);
310 log_id = nsamples >> 12;
312 if (log_id == LOG_ID_MICROPEAK2) {
313 int nsamples_high = get_16(f);
314 nsamples |= (nsamples_high << 12);
317 cal_data.set_ground_pressure(ground_pressure);
320 case LOG_ID_MICROPEAK:
321 time_step = 2 * CLOCK_MP1;
323 case LOG_ID_MICROKITE:
324 time_step = 200 * CLOCK_MP1;
326 case LOG_ID_MICROPEAK2:
327 time_step = CLOCK_MP2;
330 throw new IOException(String.format("Unknown device type: %d", log_id));
332 cal_data.set_ticks_per_sec(1/time_step);
333 cal_data.set_tick(0);
334 cal_data.set_boost_tick();
336 int cur = ground_pressure;
337 cal_data.set_tick(0);
338 flight_series.set_time(cal_data.time());
339 flight_series.set_pressure(cur);
340 for (int i = 0; i < nsamples; i++) {
342 int same = mix_in(cur, k);
343 int up = mix_in(cur + 0x10000, k);
344 int down = mix_in(cur - 0x10000, k);
346 if (closer (cur, same, up)) {
347 if (closer (cur, same, down))
352 if (closer (cur, up, down))
358 cal_data.set_tick(i+1);
359 flight_series.set_time(cal_data.time());
360 flight_series.set_pressure(cur);
363 int current_crc = swap16(~file_crc & 0xffff);
366 crc_valid = (crc == current_crc);
368 if (!crc_valid && is_empty()) {
373 if (log_id == LOG_ID_MICROPEAK2) {
374 unique_id = get_line(f);
377 flight_series.finish();
381 flight_series.set_time(0);
382 flight_series.set_state(AltosLib.ao_flight_boost);
384 if (flight_series.speed_series != null && flight_series.speed_series.max() != null) {
385 flight_series.set_time(flight_series.speed_series.max().time);
386 flight_series.set_state(AltosLib.ao_flight_coast);
389 flight_series.set_time(flight_series.height_series.max().time);
390 flight_series.set_state(AltosLib.ao_flight_drogue);
392 cal_data.set_tick(nsamples);
393 flight_series.set_time(cal_data.time());
394 flight_series.set_state(AltosLib.ao_flight_landed);
396 flight_series.set_min_pressure(min_pressure);
398 flight_series.finish();
400 flight_stats = new AltosFlightStats(flight_series);
403 } catch (FileEndedException fe) {
404 throw new IOException("File Ended Unexpectedly");