altoslib: Move CSV/KML output code to altoslib
[fw/altos] / altoslib / AltosConvert.java
1 /*
2  * Copyright © 2010 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 /*
19  * Sensor data conversion functions
20  */
21 package org.altusmetrum.altoslib_3;
22
23 public class AltosConvert {
24         /*
25          * Pressure Sensor Model, version 1.1
26          *
27          * written by Holly Grimes
28          *
29          * Uses the International Standard Atmosphere as described in
30          *   "A Quick Derivation relating altitude to air pressure" (version 1.03)
31          *    from the Portland State Aerospace Society, except that the atmosphere
32          *    is divided into layers with each layer having a different lapse rate.
33          *
34          * Lapse rate data for each layer was obtained from Wikipedia on Sept. 1, 2007
35          *    at site <http://en.wikipedia.org/wiki/International_Standard_Atmosphere
36          *
37          * Height measurements use the local tangent plane.  The postive z-direction is up.
38          *
39          * All measurements are given in SI units (Kelvin, Pascal, meter, meters/second^2).
40          *   The lapse rate is given in Kelvin/meter, the gas constant for air is given
41          *   in Joules/(kilogram-Kelvin).
42          */
43
44         public static final double GRAVITATIONAL_ACCELERATION = -9.80665;
45         public static final double AIR_GAS_CONSTANT             = 287.053;
46         public static final double NUMBER_OF_LAYERS             = 7;
47         public static final double MAXIMUM_ALTITUDE             = 84852.0;
48         public static final double MINIMUM_PRESSURE             = 0.3734;
49         public static final double LAYER0_BASE_TEMPERATURE      = 288.15;
50         public static final double LAYER0_BASE_PRESSURE = 101325;
51
52         /* lapse rate and base altitude for each layer in the atmosphere */
53         public static final double[] lapse_rate = {
54                 -0.0065, 0.0, 0.001, 0.0028, 0.0, -0.0028, -0.002
55         };
56
57         public static final int[] base_altitude = {
58                 0, 11000, 20000, 32000, 47000, 51000, 71000
59         };
60
61         /* outputs atmospheric pressure associated with the given altitude.
62          * altitudes are measured with respect to the mean sea level
63          */
64         public static double
65         altitude_to_pressure(double altitude)
66         {
67                 double base_temperature = LAYER0_BASE_TEMPERATURE;
68                 double base_pressure = LAYER0_BASE_PRESSURE;
69
70                 double pressure;
71                 double base; /* base for function to determine pressure */
72                 double exponent; /* exponent for function to determine pressure */
73                 int layer_number; /* identifies layer in the atmosphere */
74                 double delta_z; /* difference between two altitudes */
75
76                 if (altitude > MAXIMUM_ALTITUDE) /* FIX ME: use sensor data to improve model */
77                         return 0;
78
79                 /* calculate the base temperature and pressure for the atmospheric layer
80                    associated with the inputted altitude */
81                 for(layer_number = 0; layer_number < NUMBER_OF_LAYERS - 1 && altitude > base_altitude[layer_number + 1]; layer_number++) {
82                         delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
83                         if (lapse_rate[layer_number] == 0.0) {
84                                 exponent = GRAVITATIONAL_ACCELERATION * delta_z
85                                         / AIR_GAS_CONSTANT / base_temperature;
86                                 base_pressure *= Math.exp(exponent);
87                         }
88                         else {
89                                 base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
90                                 exponent = GRAVITATIONAL_ACCELERATION /
91                                         (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
92                                 base_pressure *= Math.pow(base, exponent);
93                         }
94                         base_temperature += delta_z * lapse_rate[layer_number];
95                 }
96
97                 /* calculate the pressure at the inputted altitude */
98                 delta_z = altitude - base_altitude[layer_number];
99                 if (lapse_rate[layer_number] == 0.0) {
100                         exponent = GRAVITATIONAL_ACCELERATION * delta_z
101                                 / AIR_GAS_CONSTANT / base_temperature;
102                         pressure = base_pressure * Math.exp(exponent);
103                 }
104                 else {
105                         base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
106                         exponent = GRAVITATIONAL_ACCELERATION /
107                                 (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
108                         pressure = base_pressure * Math.pow(base, exponent);
109                 }
110
111                 return pressure;
112         }
113
114
115 /* outputs the altitude associated with the given pressure. the altitude
116    returned is measured with respect to the mean sea level */
117         public static double
118         pressure_to_altitude(double pressure)
119         {
120
121                 double next_base_temperature = LAYER0_BASE_TEMPERATURE;
122                 double next_base_pressure = LAYER0_BASE_PRESSURE;
123
124                 double altitude;
125                 double base_pressure;
126                 double base_temperature;
127                 double base; /* base for function to determine base pressure of next layer */
128                 double exponent; /* exponent for function to determine base pressure
129                                     of next layer */
130                 double coefficient;
131                 int layer_number; /* identifies layer in the atmosphere */
132                 int delta_z; /* difference between two altitudes */
133
134                 if (pressure < 0)  /* illegal pressure */
135                         return -1;
136                 if (pressure < MINIMUM_PRESSURE) /* FIX ME: use sensor data to improve model */
137                         return MAXIMUM_ALTITUDE;
138
139                 /* calculate the base temperature and pressure for the atmospheric layer
140                    associated with the inputted pressure. */
141                 layer_number = -1;
142                 do {
143                         layer_number++;
144                         base_pressure = next_base_pressure;
145                         base_temperature = next_base_temperature;
146                         delta_z = base_altitude[layer_number + 1] - base_altitude[layer_number];
147                         if (lapse_rate[layer_number] == 0.0) {
148                                 exponent = GRAVITATIONAL_ACCELERATION * delta_z
149                                         / AIR_GAS_CONSTANT / base_temperature;
150                                 next_base_pressure *= Math.exp(exponent);
151                         }
152                         else {
153                                 base = (lapse_rate[layer_number] * delta_z / base_temperature) + 1.0;
154                                 exponent = GRAVITATIONAL_ACCELERATION /
155                                         (AIR_GAS_CONSTANT * lapse_rate[layer_number]);
156                                 next_base_pressure *= Math.pow(base, exponent);
157                         }
158                         next_base_temperature += delta_z * lapse_rate[layer_number];
159                 }
160                 while(layer_number < NUMBER_OF_LAYERS - 1 && pressure < next_base_pressure);
161
162                 /* calculate the altitude associated with the inputted pressure */
163                 if (lapse_rate[layer_number] == 0.0) {
164                         coefficient = (AIR_GAS_CONSTANT / GRAVITATIONAL_ACCELERATION)
165                                 * base_temperature;
166                         altitude = base_altitude[layer_number]
167                                 + coefficient * Math.log(pressure / base_pressure);
168                 }
169                 else {
170                         base = pressure / base_pressure;
171                         exponent = AIR_GAS_CONSTANT * lapse_rate[layer_number]
172                                 / GRAVITATIONAL_ACCELERATION;
173                         coefficient = base_temperature / lapse_rate[layer_number];
174                         altitude = base_altitude[layer_number]
175                                 + coefficient * (Math.pow(base, exponent) - 1);
176                 }
177
178                 return altitude;
179         }
180
181         public static double
182         cc_battery_to_voltage(double battery)
183         {
184                 return battery / 32767.0 * 5.0;
185         }
186
187         public static double
188         cc_ignitor_to_voltage(double ignite)
189         {
190                 return ignite / 32767 * 15.0;
191         }
192
193         public static double
194         barometer_to_pressure(double count)
195         {
196                 return ((count / 16.0) / 2047.0 + 0.095) / 0.009 * 1000.0;
197         }
198
199         static double
200         thermometer_to_temperature(double thermo)
201         {
202                 return (thermo - 19791.268) / 32728.0 * 1.25 / 0.00247;
203         }
204
205         static double mega_adc(int raw) {
206                 return raw / 4095.0;
207         }
208
209         static public double mega_battery_voltage(int v_batt) {
210                 if (v_batt != AltosLib.MISSING)
211                         return 3.3 * mega_adc(v_batt) * (15.0 + 27.0) / 27.0;
212                 return AltosLib.MISSING;
213         }
214
215         static double mega_pyro_voltage(int raw) {
216                 if (raw != AltosLib.MISSING)
217                         return 3.3 * mega_adc(raw) * (100.0 + 27.0) / 27.0;
218                 return AltosLib.MISSING;
219         }
220
221         static double tele_mini_voltage(int sensor) {
222                 double  supply = 3.3;
223
224                 return sensor / 32767.0 * supply * 127/27;
225         }
226
227         static double easy_mini_voltage(int sensor, int serial) {
228                 double  supply = 3.3;
229                 double  diode_offset = 0.0;
230
231                 /* early prototypes had a 3.0V regulator */
232                 if (serial < 1000)
233                         supply = 3.0;
234
235                 /* Purple v1.0 boards had the sensor after the
236                  * blocking diode, which drops about 150mV
237                  */
238                 if (serial < 1665)
239                         diode_offset = 0.150;
240
241                 return sensor / 32767.0 * supply * 127/27 + diode_offset;
242         }
243
244         public static double radio_to_frequency(int freq, int setting, int cal, int channel) {
245                 double  f;
246
247                 if (freq > 0)
248                         f = freq / 1000.0;
249                 else {
250                         if (setting <= 0)
251                                 setting = cal;
252                         f = 434.550 * setting / cal;
253                         /* Round to nearest 50KHz */
254                         f = Math.floor (20.0 * f + 0.5) / 20.0;
255                 }
256                 return f + channel * 0.100;
257         }
258
259         public static int radio_frequency_to_setting(double frequency, int cal) {
260                 double  set = frequency / 434.550 * cal;
261
262                 return (int) Math.floor (set + 0.5);
263         }
264
265         public static int radio_frequency_to_channel(double frequency) {
266                 int     channel = (int) Math.floor ((frequency - 434.550) / 0.100 + 0.5);
267
268                 if (channel < 0)
269                         channel = 0;
270                 if (channel > 9)
271                         channel = 9;
272                 return channel;
273         }
274
275         public static double radio_channel_to_frequency(int channel) {
276                 return 434.550 + channel * 0.100;
277         }
278
279         public static int[] ParseHex(String line) {
280                 String[] tokens = line.split("\\s+");
281                 int[] array = new int[tokens.length];
282
283                 for (int i = 0; i < tokens.length; i++)
284                         try {
285                                 array[i] = Integer.parseInt(tokens[i], 16);
286                         } catch (NumberFormatException ne) {
287                                 return null;
288                         }
289                 return array;
290         }
291
292         public static double meters_to_feet(double meters) {
293                 return meters * (100 / (2.54 * 12));
294         }
295
296         public static double feet_to_meters(double feet) {
297                 return feet * 12 * 2.54 / 100.0;
298         }
299
300         public static double meters_to_miles(double meters) {
301                 return meters_to_feet(meters) / 5280;
302         }
303
304         public static double miles_to_meters(double miles) {
305                 return feet_to_meters(miles * 5280);
306         }
307
308         public static double meters_to_mph(double mps) {
309                 return meters_to_miles(mps) * 3600;
310         }
311
312         public static double mph_to_meters(double mps) {
313                 return miles_to_meters(mps) / 3600;
314         }
315
316         public static double meters_to_mach(double meters) {
317                 return meters / 343;            /* something close to mach at usual rocket sites */
318         }
319
320         public static double meters_to_g(double meters) {
321                 return meters / 9.80665;
322         }
323
324         public static double c_to_f(double c) {
325                 return c * 9/5 + 32;
326         }
327
328         public static double f_to_c(double c) {
329                 return (c - 32) * 5/9;
330         }
331
332         public static boolean imperial_units = false;
333
334         public static AltosDistance distance = new AltosDistance();
335
336         public static AltosHeight height = new AltosHeight();
337
338         public static AltosSpeed speed = new AltosSpeed();
339
340         public static AltosAccel accel = new AltosAccel();
341
342         public static AltosTemperature temperature = new AltosTemperature();
343
344         public static AltosOrient orient = new AltosOrient();
345
346         public static String show_gs(String format, double a) {
347                 a = meters_to_g(a);
348                 format = format.concat(" g");
349                 return String.format(format, a);
350         }
351
352         public static String say_gs(double a) {
353                 return String.format("%6.0 gees", meters_to_g(a));
354         }
355
356         public static int checksum(int[] data, int start, int length) {
357                 int     csum = 0x5a;
358                 for (int i = 0; i < length; i++)
359                         csum += data[i + start];
360                 return csum & 0xff;
361         }
362
363         public static double beep_value_to_freq(int value) {
364                 if (value == 0)
365                         return 4000;
366                 return 1.0/2.0 * (24.0e6/32.0) / (double) value;
367         }
368
369         public static int beep_freq_to_value(double freq) {
370                 if (freq == 0)
371                         return 94;
372                 return (int) Math.floor (1.0/2.0 * (24.0e6/32.0) / freq + 0.5);
373         }
374 }