micropeak: Add MicroPeak V2 to the download code
[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_11.*;
25 import org.altusmetrum.altosuilib_11.*;
26
27 class MicroIterator implements Iterator<MicroDataPoint> {
28         int             i;
29         MicroData       data;
30
31         public boolean hasNext() {
32                 return i < data.pressures.length;
33         }
34
35         public MicroDataPoint next() {
36                 return new MicroDataPoint(data, i++);
37         }
38
39         public MicroIterator (MicroData data) {
40                 this.data = data;
41                 i = 0;
42         }
43
44         public void remove() {
45         }
46 }
47
48 class MicroIterable implements Iterable<MicroDataPoint> {
49
50         MicroData       data;
51
52         public Iterator<MicroDataPoint> iterator() {
53                 return new MicroIterator(data);
54         }
55
56         public MicroIterable(MicroData data) {
57                 this.data = data;
58         }
59 }
60
61 class MicroUIIterator implements Iterator<AltosUIDataPoint> {
62         int             i;
63         MicroData       data;
64
65         public boolean hasNext() {
66                 return i < data.pressures.length;
67         }
68
69         public AltosUIDataPoint next() {
70                 return new MicroDataPoint(data, i++);
71         }
72
73         public MicroUIIterator (MicroData data) {
74                 this.data = data;
75                 i = 0;
76         }
77
78         public void remove() {
79         }
80 }
81
82 class MicroUIIterable implements Iterable<AltosUIDataPoint> {
83         MicroData       data;
84
85         public Iterator<AltosUIDataPoint> iterator() {
86                 return new MicroUIIterator(data);
87         }
88
89         public MicroUIIterable(MicroData data) {
90                 this.data = data;
91         }
92 }
93
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;
101         public int              log_id;
102         String                  name;
103         MicroStats              stats;
104
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;
108
109         public static final double CLOCK_MP1 = 0.096;
110         public static final double CLOCK_MP2 = 0.1;
111
112         public class FileEndedException extends Exception {
113         }
114
115         public class NonHexcharException extends Exception {
116         }
117
118         public class InvalidCrcException extends Exception {
119         }
120
121         private int getc(InputStream f) throws IOException, FileEndedException {
122                 int     c = f.read();
123
124                 if (c == -1)
125                         throw new FileEndedException();
126                 bytes.add(c);
127                 return c;
128         }
129
130         private int get_nonwhite(InputStream f) throws IOException, FileEndedException {
131                 int     c;
132
133                 for (;;) {
134                         c = getc(f);
135                         if (!Character.isWhitespace(c))
136                                 return c;
137                 }
138         }
139
140         private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException {
141                 int     c = get_nonwhite(f);
142
143                 if ('0' <= c && c <= '9')
144                         return c - '0';
145                 if ('a' <= c && c <= 'f')
146                         return c - 'a' + 10;
147                 if ('A' <= c && c <= 'F')
148                         return c - 'A' + 10;
149                 throw new NonHexcharException();
150         }
151
152         private static final int POLY = 0x8408;
153
154         private int log_crc(int crc, int b) {
155                 int     i;
156
157                 for (i = 0; i < 8; i++) {
158                         if (((crc & 0x0001) ^ (b & 0x0001)) != 0)
159                                 crc = (crc >> 1) ^ POLY;
160                         else
161                                 crc = crc >> 1;
162                         b >>= 1;
163                 }
164                 return crc & 0xffff;
165         }
166
167         int     file_crc;
168
169         private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException {
170                 int     a = get_hexc(f);
171                 int     b = get_hexc(f);
172
173                 int h = (a << 4) + b;
174
175                 file_crc = log_crc(file_crc, h);
176                 return h;
177         }
178
179         private boolean find_header(InputStream f) throws IOException, FileEndedException {
180                 for (;;) {
181                         if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P')
182                                 return true;
183                 }
184         }
185
186         private int get_32(InputStream f)  throws IOException, FileEndedException, NonHexcharException {
187                 int     v = 0;
188                 for (int i = 0; i < 4; i++) {
189                         v += get_hex(f) << (i * 8);
190                 }
191                 return v;
192         }
193
194         private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException {
195                 int     v = 0;
196                 for (int i = 0; i < 2; i++) {
197                         v += get_hex(f) << (i * 8);
198                 }
199                 return v;
200         }
201
202         private int swap16(int i) {
203                 return ((i << 8) & 0xff00) | ((i >> 8) & 0xff);
204         }
205
206         public boolean  crc_valid;
207
208         int mix_in (int high, int low) {
209                 return  high - (high & 0xffff) + low;
210         }
211
212         boolean closer (int target, int a, int b) {
213                 return Math.abs (target - a) < Math.abs(target - b);
214         }
215
216         public double altitude(int i) {
217                 return AltosConvert.pressure_to_altitude(pressures[i]);
218         }
219
220         public String name() {
221                 return name;
222         }
223
224         public Iterable<AltosUIDataPoint> dataPoints() {
225                 return new MicroUIIterable(this);
226         }
227
228         public Iterable<MicroDataPoint> points() {
229                 return new MicroIterable(this);
230         }
231
232         int fact(int n) {
233                 if (n == 0)
234                         return 1;
235                 return n * fact(n-1);
236         }
237
238         int choose(int n, int k) {
239                 return fact(n) / (fact(k) * fact(n-k));
240         }
241
242
243         public double avg_altitude(int center, int dist) {
244                 int     start = center - dist;
245                 int     stop = center + dist;
246
247                 if (start < 0)
248                         start = 0;
249                 if (stop >= pressures.length)
250                         stop = pressures.length - 1;
251
252                 double  sum = 0;
253                 double  div = 0;
254
255                 int     n = dist * 2;
256
257                 for (int i = start; i <= stop; i++) {
258                         int     k = i - (center - dist);
259                         int     c = choose (n, k);
260
261                         sum += c * pressures[i];
262                         div += c;
263                 }
264
265                 double pres = sum / div;
266
267                 double alt = AltosConvert.pressure_to_altitude(pres);
268                 return alt;
269         }
270
271         public double pressure(int i) {
272                 return pressures[i];
273         }
274
275         public double height(int i) {
276                 return altitude(i) - ground_altitude;
277         }
278
279         public double apogee_pressure() {
280                 return min_pressure;
281         }
282
283         public double apogee_altitude() {
284                 return AltosConvert.pressure_to_altitude(apogee_pressure());
285         }
286
287         public double apogee_height() {
288                 return apogee_altitude() - ground_altitude;
289         }
290
291         static final int speed_avg = 3;
292         static final int accel_avg = 5;
293
294         private double avg_speed(int center, int dist) {
295                 if (center == 0)
296                         return 0;
297
298                 double ai = avg_altitude(center, dist);
299                 double aj = avg_altitude(center - 1, dist);
300                 double s = (ai - aj) / time_step;
301
302                 return s;
303         }
304
305         public double speed(int i) {
306                 return avg_speed(i, speed_avg);
307         }
308
309         public double acceleration(int i) {
310                 if (i == 0)
311                         return 0;
312                 return (avg_speed(i, accel_avg) - avg_speed(i-1, accel_avg)) / time_step;
313         }
314
315         public double time(int i) {
316                 return i * time_step;
317         }
318
319         public void save (OutputStream f) throws IOException {
320                 for (int c : bytes)
321                         f.write(c);
322                 f.write('\n');
323         }
324
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",
330                                   point.time,
331                                   point.pressure,
332                                   point.height,
333                                   AltosConvert.meters_to_feet(point.height),
334                                   point.speed,
335                                   AltosConvert.meters_to_mph(point.speed),
336                                   AltosConvert.meters_to_mach(point.speed),
337                                   point.accel,
338                                   AltosConvert.meters_to_feet(point.accel),
339                                   AltosConvert.meters_to_g(point.accel));
340                 }
341         }
342
343         public void set_name(String name) {
344                 this.name = name;
345         }
346
347         public MicroData (InputStream f, String name) throws IOException, InterruptedException, NonHexcharException, FileEndedException {
348                 this.name = name;
349                 bytes = new ArrayList<Integer>();
350                 if (!find_header(f))
351                         throw new IOException("No MicroPeak data header found");
352                 try {
353                         file_crc = 0xffff;
354                         ground_pressure = get_32(f);
355                         min_pressure = get_32(f);
356                         int nsamples = get_16(f);
357
358                         log_id = nsamples >> 12;
359                         nsamples &= 0xfff;
360                         pressures = new int[nsamples + 1];
361
362                         ground_altitude = AltosConvert.pressure_to_altitude(ground_pressure);
363                         int cur = ground_pressure;
364                         pressures[0] = cur;
365                         for (int i = 0; i < nsamples; i++) {
366                                 int     k = get_16(f);
367                                 int     same = mix_in(cur, k);
368                                 int     up = mix_in(cur + 0x10000, k);
369                                 int     down = mix_in(cur - 0x10000, k);
370
371                                 if (closer (cur, same, up)) {
372                                         if (closer (cur, same, down))
373                                                 cur = same;
374                                         else
375                                                 cur = down;
376                                 } else {
377                                         if (closer (cur, up, down))
378                                                 cur = up;
379                                         else
380                                                 cur = down;
381                                 }
382
383                                 pressures[i+1] = cur;
384                         }
385
386                         int current_crc = swap16(~file_crc & 0xffff);
387                         int crc = get_16(f);
388
389                         crc_valid = crc == current_crc;
390
391                         switch (log_id) {
392                         case LOG_ID_MICROPEAK:
393                                 time_step = 2 * CLOCK_MP1;
394                                 break;
395                         case LOG_ID_MICROKITE:
396                                 time_step = 200 * CLOCK_MP1;
397                                 break;
398                         case LOG_ID_MICROPEAK2:
399                                 time_step = CLOCK_MP2;
400                                 break;
401                         default:
402                                 throw new IOException(String.format("Unknown device type: %d", log_id));
403                         }
404                         stats = new MicroStats(this);
405                 } catch (FileEndedException fe) {
406                         throw new IOException("File Ended Unexpectedly");
407                 }
408         }
409
410         public MicroData() {
411                 ground_pressure = 101000;
412                 min_pressure = 101000;
413                 pressures = new int[1];
414                 pressures[0] = 101000;
415         }
416
417 }