ao-tools/ao-dump-up: Document --wait flag
[fw/altos] / micropeak / MicroData.java
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 package org.altusmetrum.micropeak;
20
21 import java.lang.*;
22 import java.io.*;
23 import java.util.*;
24 import org.altusmetrum.altoslib_13.*;
25 import org.altusmetrum.altosuilib_13.*;
26
27 public class MicroData {
28         public int              ground_pressure;
29         public int              min_pressure;
30
31         AltosUIFlightSeries     flight_series;
32         AltosFlightStats        flight_stats;
33         AltosCalData            cal_data;
34
35         private double          time_step;
36         private ArrayList<Integer>      bytes;
37         public int              log_id;
38         String                  name;
39
40         public static final int LOG_ID_MICROPEAK = 0;
41         public static final int LOG_ID_MICROKITE = 1;
42         public static final int LOG_ID_MICROPEAK2 = 2;
43
44         public static final double CLOCK_MP1 = 0.096;
45         public static final double CLOCK_MP2 = 0.1;
46
47         public class FileEndedException extends Exception {
48         }
49
50         public class NonHexcharException extends Exception {
51         }
52
53         public class InvalidCrcException extends Exception {
54         }
55
56         private int getc(InputStream f) throws IOException, FileEndedException {
57                 int     c = f.read();
58
59                 if (c == -1)
60                         throw new FileEndedException();
61                 bytes.add(c);
62                 return c;
63         }
64
65         private int get_nonwhite(InputStream f) throws IOException, FileEndedException {
66                 int     c;
67
68                 for (;;) {
69                         c = getc(f);
70                         if (!Character.isWhitespace(c))
71                                 return c;
72                 }
73         }
74
75         private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException {
76                 int     c = get_nonwhite(f);
77
78                 if ('0' <= c && c <= '9')
79                         return c - '0';
80                 if ('a' <= c && c <= 'f')
81                         return c - 'a' + 10;
82                 if ('A' <= c && c <= 'F')
83                         return c - 'A' + 10;
84                 throw new NonHexcharException();
85         }
86
87         private static final int POLY = 0x8408;
88
89         private int log_crc(int crc, int b) {
90                 int     i;
91
92                 for (i = 0; i < 8; i++) {
93                         if (((crc & 0x0001) ^ (b & 0x0001)) != 0)
94                                 crc = (crc >> 1) ^ POLY;
95                         else
96                                 crc = crc >> 1;
97                         b >>= 1;
98                 }
99                 return crc & 0xffff;
100         }
101
102         int     file_crc;
103
104         private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException {
105                 int     a = get_hexc(f);
106                 int     b = get_hexc(f);
107
108                 int h = (a << 4) + b;
109
110                 file_crc = log_crc(file_crc, h);
111                 return h;
112         }
113
114         private boolean find_header(InputStream f) throws IOException, FileEndedException {
115                 for (;;) {
116                         if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P')
117                                 return true;
118                 }
119         }
120
121         private int get_32(InputStream f)  throws IOException, FileEndedException, NonHexcharException {
122                 int     v = 0;
123                 for (int i = 0; i < 4; i++) {
124                         v += get_hex(f) << (i * 8);
125                 }
126                 return v;
127         }
128
129         private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException {
130                 int     v = 0;
131                 for (int i = 0; i < 2; i++) {
132                         v += get_hex(f) << (i * 8);
133                 }
134                 return v;
135         }
136
137         private int swap16(int i) {
138                 return ((i << 8) & 0xff00) | ((i >> 8) & 0xff);
139         }
140
141         public boolean  crc_valid;
142
143         int mix_in (int high, int low) {
144                 return  high - (high & 0xffff) + low;
145         }
146
147         boolean closer (int target, int a, int b) {
148                 return Math.abs (target - a) < Math.abs(target - b);
149         }
150
151         public double altitude(double time) {
152                 if (flight_series.altitude_series == null)
153                         return 0.0;
154                 return flight_series.altitude_series.value(time);
155         }
156
157         public double altitude(int i) {
158                 return altitude(time(i));
159         }
160
161         public String name() {
162                 return name;
163         }
164
165         public double pressure(int i) {
166                 if (flight_series.pressure_series == null)
167                         return 0.0;
168
169                 return flight_series.pressure_series.value(time(i));
170         }
171
172         public double height(double time) {
173                 if (flight_series.height_series == null)
174                         return 0.0;
175
176                 return flight_series.height_series.value(time);
177         }
178
179         public double height(int i) {
180                 return height(time(i));
181         }
182
183         public int length() {
184                 if (flight_series.pressure_series == null)
185                         return 0;
186                 return flight_series.pressure_series.size();
187         }
188
189         /* Use the recorded apogee pressure for stats so that it agrees with the device */
190         public double apogee_pressure() {
191                 return min_pressure;
192         }
193
194         public double apogee_altitude() {
195                 return AltosConvert.pressure_to_altitude(apogee_pressure());
196         }
197
198         public double apogee_height() {
199                 return apogee_altitude() - cal_data.ground_altitude;
200         }
201
202         public double speed(double time) {
203                 if (flight_series.speed_series == null)
204                         return 0.0;
205                 return flight_series.speed_series.value(time);
206         }
207
208         public double speed(int i) {
209                 return speed(time(i));
210         }
211
212         public double acceleration(double time) {
213                 if (flight_series.accel_series == null)
214                         return 0.0;
215                 return flight_series.accel_series.value(time);
216         }
217
218         public double acceleration(int i) {
219                 return acceleration(time(i));
220         }
221
222         public double time(int i) {
223                 return i * time_step;
224         }
225
226         public void save (OutputStream f) throws IOException {
227                 for (int c : bytes)
228                         f.write(c);
229                 f.write('\n');
230         }
231
232         public void export (Writer f) throws IOException {
233                 PrintWriter     pw = new PrintWriter(f);
234                 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");
235
236                 for (AltosTimeValue ptv : flight_series.pressure_series) {
237
238                         double height = height(ptv.time);
239                         double speed = speed(ptv.time);
240                         double accel = acceleration(ptv.time);
241
242                         pw.printf("%6.3f,%10.0f,%10.1f,%10.1f,%11.2f,%11.2f,%12.4f,%12.2f,%13.2f,%10.4f\n",
243                                   ptv.time,
244                                   ptv.value,
245                                   height,
246                                   AltosConvert.meters_to_feet(height),
247                                   speed,
248                                   AltosConvert.meters_to_mph(speed),
249                                   AltosConvert.meters_to_mach(speed),
250                                   accel,
251                                   AltosConvert.meters_to_feet(accel),
252                                   AltosConvert.meters_to_g(accel));
253                 }
254         }
255
256         public void set_name(String name) {
257                 this.name = name;
258         }
259
260         public MicroData() {
261                 ground_pressure = 101000;
262                 min_pressure = 101000;
263                 cal_data = new AltosCalData();
264                 flight_series = new AltosUIFlightSeries(cal_data);
265         }
266
267         public MicroData (InputStream f, String name) throws IOException, InterruptedException, NonHexcharException, FileEndedException {
268                 this.name = name;
269                 bytes = new ArrayList<Integer>();
270
271                 cal_data = new AltosCalData();
272                 flight_series = new AltosUIFlightSeries(cal_data);
273
274                 if (!find_header(f))
275                         throw new IOException("No MicroPeak data header found");
276                 try {
277                         file_crc = 0xffff;
278                         ground_pressure = get_32(f);
279                         min_pressure = get_32(f);
280                         int nsamples = get_16(f);
281
282                         log_id = nsamples >> 12;
283                         nsamples &= 0xfff;
284
285                         cal_data.set_ground_pressure(ground_pressure);
286
287                         switch (log_id) {
288                         case LOG_ID_MICROPEAK:
289                                 time_step = 2 * CLOCK_MP1;
290                                 break;
291                         case LOG_ID_MICROKITE:
292                                 time_step = 200 * CLOCK_MP1;
293                                 break;
294                         case LOG_ID_MICROPEAK2:
295                                 time_step = CLOCK_MP2;
296                                 break;
297                         default:
298                                 throw new IOException(String.format("Unknown device type: %d", log_id));
299                         }
300                         cal_data.set_ticks_per_sec(1/time_step);
301                         cal_data.set_tick(0);
302                         cal_data.set_boost_tick();
303
304                         int cur = ground_pressure;
305                         cal_data.set_tick(0);
306                         flight_series.set_time(cal_data.time());
307                         flight_series.set_pressure(cur);
308                         for (int i = 0; i < nsamples; i++) {
309                                 int     k = get_16(f);
310                                 int     same = mix_in(cur, k);
311                                 int     up = mix_in(cur + 0x10000, k);
312                                 int     down = mix_in(cur - 0x10000, k);
313
314                                 if (closer (cur, same, up)) {
315                                         if (closer (cur, same, down))
316                                                 cur = same;
317                                         else
318                                                 cur = down;
319                                 } else {
320                                         if (closer (cur, up, down))
321                                                 cur = up;
322                                         else
323                                                 cur = down;
324                                 }
325
326                                 cal_data.set_tick(i+1);
327                                 flight_series.set_time(cal_data.time());
328                                 flight_series.set_pressure(cur);
329                         }
330
331                         flight_series.finish();
332
333                         /* Build states */
334
335                         flight_series.set_time(0);
336                         flight_series.set_state(AltosLib.ao_flight_boost);
337
338                         flight_series.set_time(flight_series.speed_series.max().time);
339                         flight_series.set_state(AltosLib.ao_flight_coast);
340
341                         flight_series.set_time(flight_series.height_series.max().time);
342                         flight_series.set_state(AltosLib.ao_flight_drogue);
343
344                         cal_data.set_tick(nsamples);
345                         flight_series.set_time(cal_data.time());
346                         flight_series.set_state(AltosLib.ao_flight_landed);
347
348                         flight_series.set_min_pressure(min_pressure);
349
350                         flight_series.finish();
351
352                         flight_stats = new AltosFlightStats(flight_series);
353
354                         int current_crc = swap16(~file_crc & 0xffff);
355                         int crc = get_16(f);
356
357                         crc_valid = crc == current_crc;
358
359                 } catch (FileEndedException fe) {
360                         throw new IOException("File Ended Unexpectedly");
361                 }
362         }
363
364 }