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