Switch from GPLv2 to GPLv2+
[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
108         public static final double CLOCK = 0.096;
109
110         public class FileEndedException extends Exception {
111         }
112
113         public class NonHexcharException extends Exception {
114         }
115
116         public class InvalidCrcException extends Exception {
117         }
118
119         private int getc(InputStream f) throws IOException, FileEndedException {
120                 int     c = f.read();
121
122                 if (c == -1)
123                         throw new FileEndedException();
124                 bytes.add(c);
125                 return c;
126         }
127
128         private int get_nonwhite(InputStream f) throws IOException, FileEndedException {
129                 int     c;
130
131                 for (;;) {
132                         c = getc(f);
133                         if (!Character.isWhitespace(c))
134                                 return c;
135                 }
136         }
137
138         private int get_hexc(InputStream f) throws IOException, FileEndedException, NonHexcharException {
139                 int     c = get_nonwhite(f);
140
141                 if ('0' <= c && c <= '9')
142                         return c - '0';
143                 if ('a' <= c && c <= 'f')
144                         return c - 'a' + 10;
145                 if ('A' <= c && c <= 'F')
146                         return c - 'A' + 10;
147                 throw new NonHexcharException();
148         }
149
150         private static final int POLY = 0x8408;
151
152         private int log_crc(int crc, int b) {
153                 int     i;
154
155                 for (i = 0; i < 8; i++) {
156                         if (((crc & 0x0001) ^ (b & 0x0001)) != 0)
157                                 crc = (crc >> 1) ^ POLY;
158                         else
159                                 crc = crc >> 1;
160                         b >>= 1;
161                 }
162                 return crc & 0xffff;
163         }
164
165         int     file_crc;
166
167         private int get_hex(InputStream f) throws IOException, FileEndedException, NonHexcharException {
168                 int     a = get_hexc(f);
169                 int     b = get_hexc(f);
170
171                 int h = (a << 4) + b;
172
173                 file_crc = log_crc(file_crc, h);
174                 return h;
175         }
176
177         private boolean find_header(InputStream f) throws IOException, FileEndedException {
178                 for (;;) {
179                         if (get_nonwhite(f) == 'M' && get_nonwhite(f) == 'P')
180                                 return true;
181                 }
182         }
183
184         private int get_32(InputStream f)  throws IOException, FileEndedException, NonHexcharException {
185                 int     v = 0;
186                 for (int i = 0; i < 4; i++) {
187                         v += get_hex(f) << (i * 8);
188                 }
189                 return v;
190         }
191
192         private int get_16(InputStream f) throws IOException, FileEndedException, NonHexcharException {
193                 int     v = 0;
194                 for (int i = 0; i < 2; i++) {
195                         v += get_hex(f) << (i * 8);
196                 }
197                 return v;
198         }
199
200         private int swap16(int i) {
201                 return ((i << 8) & 0xff00) | ((i >> 8) & 0xff);
202         }
203
204         public boolean  crc_valid;
205
206         int mix_in (int high, int low) {
207                 return  high - (high & 0xffff) + low;
208         }
209
210         boolean closer (int target, int a, int b) {
211                 return Math.abs (target - a) < Math.abs(target - b);
212         }
213
214         public double altitude(int i) {
215                 return AltosConvert.pressure_to_altitude(pressures[i]);
216         }
217
218         public String name() {
219                 return name;
220         }
221
222         public Iterable<AltosUIDataPoint> dataPoints() {
223                 return new MicroUIIterable(this);
224         }
225
226         public Iterable<MicroDataPoint> points() {
227                 return new MicroIterable(this);
228         }
229
230         int fact(int n) {
231                 if (n == 0)
232                         return 1;
233                 return n * fact(n-1);
234         }
235
236         int choose(int n, int k) {
237                 return fact(n) / (fact(k) * fact(n-k));
238         }
239
240
241         public double avg_altitude(int center, int dist) {
242                 int     start = center - dist;
243                 int     stop = center + dist;
244
245                 if (start < 0)
246                         start = 0;
247                 if (stop >= pressures.length)
248                         stop = pressures.length - 1;
249
250                 double  sum = 0;
251                 double  div = 0;
252
253                 int     n = dist * 2;
254
255                 for (int i = start; i <= stop; i++) {
256                         int     k = i - (center - dist);
257                         int     c = choose (n, k);
258
259                         sum += c * pressures[i];
260                         div += c;
261                 }
262
263                 double pres = sum / div;
264
265                 double alt = AltosConvert.pressure_to_altitude(pres);
266                 return alt;
267         }
268
269         public double pressure(int i) {
270                 return pressures[i];
271         }
272
273         public double height(int i) {
274                 return altitude(i) - ground_altitude;
275         }
276
277         public double apogee_pressure() {
278                 return min_pressure;
279         }
280
281         public double apogee_altitude() {
282                 return AltosConvert.pressure_to_altitude(apogee_pressure());
283         }
284
285         public double apogee_height() {
286                 return apogee_altitude() - ground_altitude;
287         }
288
289         static final int speed_avg = 3;
290         static final int accel_avg = 5;
291
292         private double avg_speed(int center, int dist) {
293                 if (center == 0)
294                         return 0;
295
296                 double ai = avg_altitude(center, dist);
297                 double aj = avg_altitude(center - 1, dist);
298                 double s = (ai - aj) / time_step;
299
300                 return s;
301         }
302
303         public double speed(int i) {
304                 return avg_speed(i, speed_avg);
305         }
306
307         public double acceleration(int i) {
308                 if (i == 0)
309                         return 0;
310                 return (avg_speed(i, accel_avg) - avg_speed(i-1, accel_avg)) / time_step;
311         }
312
313         public double time(int i) {
314                 return i * time_step;
315         }
316
317         public void save (OutputStream f) throws IOException {
318                 for (int c : bytes)
319                         f.write(c);
320                 f.write('\n');
321         }
322
323         public void export (Writer f) throws IOException {
324                 PrintWriter     pw = new PrintWriter(f);
325                 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");
326                 for (MicroDataPoint point : points()) {
327                         pw.printf("%6.3f,%10.0f,%10.1f,%10.1f,%11.2f,%11.2f,%12.4f,%12.2f,%13.2f,%10.4f\n",
328                                   point.time,
329                                   point.pressure,
330                                   point.height,
331                                   AltosConvert.meters_to_feet(point.height),
332                                   point.speed,
333                                   AltosConvert.meters_to_mph(point.speed),
334                                   AltosConvert.meters_to_mach(point.speed),
335                                   point.accel,
336                                   AltosConvert.meters_to_feet(point.accel),
337                                   AltosConvert.meters_to_g(point.accel));
338                 }
339         }
340
341         public void set_name(String name) {
342                 this.name = name;
343         }
344
345         public MicroData (InputStream f, String name) throws IOException, InterruptedException, NonHexcharException, FileEndedException {
346                 this.name = name;
347                 bytes = new ArrayList<Integer>();
348                 if (!find_header(f))
349                         throw new IOException("No MicroPeak data header found");
350                 try {
351                         file_crc = 0xffff;
352                         ground_pressure = get_32(f);
353                         min_pressure = get_32(f);
354                         int nsamples = get_16(f);
355
356                         log_id = nsamples >> 12;
357                         nsamples &= 0xfff;
358                         pressures = new int[nsamples + 1];
359
360                         ground_altitude = AltosConvert.pressure_to_altitude(ground_pressure);
361                         int cur = ground_pressure;
362                         pressures[0] = cur;
363                         for (int i = 0; i < nsamples; i++) {
364                                 int     k = get_16(f);
365                                 int     same = mix_in(cur, k);
366                                 int     up = mix_in(cur + 0x10000, k);
367                                 int     down = mix_in(cur - 0x10000, k);
368
369                                 if (closer (cur, same, up)) {
370                                         if (closer (cur, same, down))
371                                                 cur = same;
372                                         else
373                                                 cur = down;
374                                 } else {
375                                         if (closer (cur, up, down))
376                                                 cur = up;
377                                         else
378                                                 cur = down;
379                                 }
380
381                                 pressures[i+1] = cur;
382                         }
383
384                         int current_crc = swap16(~file_crc & 0xffff);
385                         int crc = get_16(f);
386
387                         crc_valid = crc == current_crc;
388
389                         switch (log_id) {
390                         case LOG_ID_MICROPEAK:
391                                 time_step = 2 * CLOCK;
392                                 break;
393                         case LOG_ID_MICROKITE:
394                                 time_step = 200 * CLOCK;
395                                 break;
396                         }
397                         stats = new MicroStats(this);
398                 } catch (FileEndedException fe) {
399                         throw new IOException("File Ended Unexpectedly");
400                 }
401         }
402
403         public MicroData() {
404                 ground_pressure = 101000;
405                 min_pressure = 101000;
406                 pressures = new int[1];
407                 pressures[0] = 101000;
408         }
409
410 }