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