2 * Copyright © 2010 Keith Packard <keithp@keithp.com>
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.
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.
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.
22 import java.util.HashMap;
25 * Telemetry data contents
30 * Version 4 is a replacement with consistent syntax. Each telemetry line
31 * contains a sequence of space-separated names and values, the values are
32 * either integers or strings. The names are all unique. All values are
35 * VERSION 4 c KD7SQG n 236 f 18 r -25 s pad t 513 r_a 15756 r_b 26444 r_t 20944
36 * r_v 26640 r_d 512 r_m 208 c_a 15775 c_b 26439 c_p 15749 c_m 16281 a_a 15764
37 * a_s 0 a_b 26439 g_s u g_n 0 s_n 0
39 * VERSION 4 c KD7SQG n 19 f 0 r -23 s pad t 513 r_b 26372 r_t 21292 r_v 26788
40 * r_d 136 r_m 140 c_b 26370 k_h 0 k_s 0 k_a 0
42 * General header fields
46 * VERSION Telemetry version number (4 or more). Must be first.
47 * c Callsign (string, no spaces allowed)
48 * n Flight unit serial number (integer)
49 * f Flight number (integer)
50 * r Packet RSSI value (integer)
51 * s Flight computer state (string, no spaces allowed)
52 * t Flight computer clock (integer in centiseconds)
54 * Version 3 is Version 2 with fixed RSSI numbers -- the radio reports
55 * in 1/2dB increments while this protocol provides only integers. So,
56 * the syntax didn't change just the interpretation of the RSSI
59 * Version 2 of the telemetry data stream is a bit of a mess, with no
60 * consistent formatting. In particular, the GPS data is formatted for
61 * viewing instead of parsing. However, the key feature is that every
62 * telemetry line contains all of the information necessary to
63 * describe the current rocket state, including the calibration values
64 * for accelerometer and barometer.
68 * VERSION 2 CALL KB0G SERIAL 51 FLIGHT 2 RSSI -68 STATUS ff STATE pad 1001 \
69 * a: 16032 p: 21232 t: 20284 v: 25160 d: 204 m: 204 fa: 16038 ga: 16032 fv: 0 \
70 * fp: 21232 gp: 21230 a+: 16049 a-: 16304 GPS 0 sat unlocked SAT 1 15 30
74 * VERSION 2 CALL KB0G SERIAL 51 FLIGHT 2 RSSI -71 STATUS ff STATE pad 2504 \
75 * a: 16028 p: 21220 t: 20360 v: 25004 d: 208 m: 200 fa: 16031 ga: 16032 fv: 330 \
76 * fp: 21231 gp: 21230 a+: 16049 a-: 16304 \
77 * GPS 9 sat 2010-02-13 17:16:51 35°20.0803'N 106°45.2235'W 1790m \
78 * 0.00m/s(H) 0° 0.00m/s(V) 1.0(hdop) 0(herr) 0(verr) \
79 * SAT 10 29 30 24 28 5 25 21 20 15 33 1 23 30 24 18 26 10 29 2 26
83 public class AltosTelemetry extends AltosRecord {
85 * General header fields
89 * VERSION Telemetry version number (4 or more). Must be first.
90 * c Callsign (string, no spaces allowed)
91 * n Flight unit serial number (integer)
92 * f Flight number (integer)
93 * r Packet RSSI value (integer)
94 * s Flight computer state (string, no spaces allowed)
95 * t Flight computer clock (integer in centiseconds)
98 final static String AO_TELEM_VERSION = "VERSION";
99 final static String AO_TELEM_CALL = "c";
100 final static String AO_TELEM_SERIAL = "n";
101 final static String AO_TELEM_FLIGHT = "f";
102 final static String AO_TELEM_RSSI = "r";
103 final static String AO_TELEM_STATE = "s";
104 final static String AO_TELEM_TICK = "t";
110 * r_a Accelerometer reading (integer)
111 * r_b Barometer reading (integer)
112 * r_t Thermometer reading (integer)
113 * r_v Battery reading (integer)
114 * r_d Drogue continuity (integer)
115 * r_m Main continuity (integer)
118 final static String AO_TELEM_RAW_ACCEL = "r_a";
119 final static String AO_TELEM_RAW_BARO = "r_b";
120 final static String AO_TELEM_RAW_THERMO = "r_t";
121 final static String AO_TELEM_RAW_BATT = "r_v";
122 final static String AO_TELEM_RAW_DROGUE = "r_d";
123 final static String AO_TELEM_RAW_MAIN = "r_m";
126 * Sensor calibration values
129 * c_a Ground accelerometer reading (integer)
130 * c_b Ground barometer reading (integer)
131 * c_p Accelerometer reading for +1g
132 * c_m Accelerometer reading for -1g
135 final static String AO_TELEM_CAL_ACCEL_GROUND = "c_a";
136 final static String AO_TELEM_CAL_BARO_GROUND = "c_b";
137 final static String AO_TELEM_CAL_ACCEL_PLUS = "c_p";
138 final static String AO_TELEM_CAL_ACCEL_MINUS = "c_m";
141 * Kalman state values
144 * k_h Height above pad (integer, meters)
145 * k_s Vertical speeed (integer, m/s * 16)
146 * k_a Vertical acceleration (integer, m/s² * 16)
149 final static String AO_TELEM_KALMAN_HEIGHT = "k_h";
150 final static String AO_TELEM_KALMAN_SPEED = "k_s";
151 final static String AO_TELEM_KALMAN_ACCEL = "k_a";
154 * Ad-hoc flight values
157 * a_a Acceleration (integer, sensor units)
158 * a_s Speed (integer, integrated acceleration value)
159 * a_b Barometer reading (integer, sensor units)
162 final static String AO_TELEM_ADHOC_ACCEL = "a_a";
163 final static String AO_TELEM_ADHOC_SPEED = "a_s";
164 final static String AO_TELEM_ADHOC_BARO = "a_b";
170 * g_s GPS state (string):
173 * e error (missing or broken)
174 * g_n Number of sats used in solution
175 * g_ns Latitude (degrees * 10e7)
176 * g_ew Longitude (degrees * 10e7)
177 * g_a Altitude (integer meters)
178 * g_Y GPS year (integer)
179 * g_M GPS month (integer - 1-12)
180 * g_D GPS day (integer - 1-31)
181 * g_h GPS hour (integer - 0-23)
182 * g_m GPS minute (integer - 0-59)
183 * g_s GPS second (integer - 0-59)
184 * g_v GPS vertical speed (integer, cm/sec)
185 * g_s GPS horizontal speed (integer, cm/sec)
186 * g_c GPS course (integer, 0-359)
187 * g_hd GPS hdop (integer * 10)
188 * g_vd GPS vdop (integer * 10)
189 * g_he GPS h error (integer)
190 * g_ve GPS v error (integer)
193 final static String AO_TELEM_GPS_STATE = "g";
194 final static String AO_TELEM_GPS_STATE_LOCKED = "l";
195 final static String AO_TELEM_GPS_STATE_UNLOCKED = "u";
196 final static String AO_TELEM_GPS_STATE_ERROR = "e";
197 final static String AO_TELEM_GPS_NUM_SAT = "g_n";
198 final static String AO_TELEM_GPS_LATITUDE = "g_ns";
199 final static String AO_TELEM_GPS_LONGITUDE = "g_ew";
200 final static String AO_TELEM_GPS_ALTITUDE = "g_a";
201 final static String AO_TELEM_GPS_YEAR = "g_Y";
202 final static String AO_TELEM_GPS_MONTH = "g_M";
203 final static String AO_TELEM_GPS_DAY = "g_D";
204 final static String AO_TELEM_GPS_HOUR = "g_h";
205 final static String AO_TELEM_GPS_MINUTE = "g_m";
206 final static String AO_TELEM_GPS_SECOND = "g_s";
207 final static String AO_TELEM_GPS_VERTICAL_SPEED = "g_v";
208 final static String AO_TELEM_GPS_HORIZONTAL_SPEED = "g_g";
209 final static String AO_TELEM_GPS_COURSE = "g_c";
210 final static String AO_TELEM_GPS_HDOP = "g_hd";
211 final static String AO_TELEM_GPS_VDOP = "g_vd";
212 final static String AO_TELEM_GPS_HERROR = "g_he";
213 final static String AO_TELEM_GPS_VERROR = "g_ve";
216 * GPS satellite values
219 * s_n Number of satellites reported (integer)
220 * s_v0 Space vehicle ID (integer) for report 0
221 * s_c0 C/N0 number (integer) for report 0
222 * s_v1 Space vehicle ID (integer) for report 1
223 * s_c1 C/N0 number (integer) for report 1
227 final static String AO_TELEM_SAT_NUM = "s_n";
228 final static String AO_TELEM_SAT_SVID = "s_v";
229 final static String AO_TELEM_SAT_C_N_0 = "s_c";
231 private void parse_v4(String[] words, int i) throws ParseException {
232 AltosTelemetryMap map = new AltosTelemetryMap(words, i);
234 callsign = map.get_string(AO_TELEM_CALL, "N0CALL");
235 serial = map.get_int(AO_TELEM_SERIAL, MISSING);
236 flight = map.get_int(AO_TELEM_FLIGHT, MISSING);
237 rssi = map.get_int(AO_TELEM_RSSI, MISSING);
238 state = Altos.state(map.get_string(AO_TELEM_STATE, "invalid"));
239 tick = map.get_int(AO_TELEM_TICK, 0);
241 /* raw sensor values */
242 accel = map.get_int(AO_TELEM_RAW_ACCEL, MISSING);
243 pres = map.get_int(AO_TELEM_RAW_BARO, MISSING);
244 temp = map.get_int(AO_TELEM_RAW_THERMO, MISSING);
245 batt = map.get_int(AO_TELEM_RAW_BATT, MISSING);
246 drogue = map.get_int(AO_TELEM_RAW_DROGUE, MISSING);
247 main = map.get_int(AO_TELEM_RAW_MAIN, MISSING);
249 /* sensor calibration information */
250 ground_accel = map.get_int(AO_TELEM_CAL_ACCEL_GROUND, MISSING);
251 ground_pres = map.get_int(AO_TELEM_CAL_BARO_GROUND, MISSING);
252 accel_plus_g = map.get_int(AO_TELEM_CAL_ACCEL_PLUS, MISSING);
253 accel_minus_g = map.get_int(AO_TELEM_CAL_ACCEL_MINUS, MISSING);
255 /* flight computer values */
256 acceleration = map.get_double(AO_TELEM_KALMAN_ACCEL, MISSING, 1/16.0);
257 speed = map.get_double(AO_TELEM_KALMAN_SPEED, MISSING, 1/16.0);
258 height = map.get_int(AO_TELEM_KALMAN_HEIGHT, MISSING);
260 flight_accel = map.get_int(AO_TELEM_ADHOC_ACCEL, MISSING);
261 flight_vel = map.get_int(AO_TELEM_ADHOC_SPEED, MISSING);
262 flight_pres = map.get_int(AO_TELEM_ADHOC_BARO, MISSING);
264 if (map.has(AO_TELEM_GPS_STATE))
265 gps = new AltosGPS(map);
270 private void parse_legacy(String[] words, int i) throws ParseException {
272 AltosParse.word (words[i++], "CALL");
273 callsign = words[i++];
275 AltosParse.word (words[i++], "SERIAL");
276 serial = AltosParse.parse_int(words[i++]);
279 AltosParse.word (words[i++], "FLIGHT");
280 flight = AltosParse.parse_int(words[i++]);
284 AltosParse.word(words[i++], "RSSI");
285 rssi = AltosParse.parse_int(words[i++]);
287 /* Older telemetry data had mis-computed RSSI value */
289 rssi = (rssi + 74) / 2 - 74;
291 AltosParse.word(words[i++], "STATUS");
292 status = AltosParse.parse_hex(words[i++]);
294 AltosParse.word(words[i++], "STATE");
295 state = Altos.state(words[i++]);
297 tick = AltosParse.parse_int(words[i++]);
299 AltosParse.word(words[i++], "a:");
300 accel = AltosParse.parse_int(words[i++]);
302 AltosParse.word(words[i++], "p:");
303 pres = AltosParse.parse_int(words[i++]);
305 AltosParse.word(words[i++], "t:");
306 temp = AltosParse.parse_int(words[i++]);
308 AltosParse.word(words[i++], "v:");
309 batt = AltosParse.parse_int(words[i++]);
311 AltosParse.word(words[i++], "d:");
312 drogue = AltosParse.parse_int(words[i++]);
314 AltosParse.word(words[i++], "m:");
315 main = AltosParse.parse_int(words[i++]);
317 AltosParse.word(words[i++], "fa:");
318 flight_accel = AltosParse.parse_int(words[i++]);
320 AltosParse.word(words[i++], "ga:");
321 ground_accel = AltosParse.parse_int(words[i++]);
323 AltosParse.word(words[i++], "fv:");
324 flight_vel = AltosParse.parse_int(words[i++]);
326 AltosParse.word(words[i++], "fp:");
327 flight_pres = AltosParse.parse_int(words[i++]);
329 AltosParse.word(words[i++], "gp:");
330 ground_pres = AltosParse.parse_int(words[i++]);
333 AltosParse.word(words[i++], "a+:");
334 accel_plus_g = AltosParse.parse_int(words[i++]);
336 AltosParse.word(words[i++], "a-:");
337 accel_minus_g = AltosParse.parse_int(words[i++]);
339 accel_plus_g = ground_accel;
340 accel_minus_g = ground_accel + 530;
343 gps = new AltosGPS(words, i, version);
346 public AltosTelemetry(String line) throws ParseException, AltosCRCException {
347 String[] words = line.split("\\s+");
350 if (words[i].equals("CRC") && words[i+1].equals("INVALID")) {
352 AltosParse.word(words[i++], "RSSI");
353 rssi = AltosParse.parse_int(words[i++]);
354 throw new AltosCRCException(rssi);
356 if (words[i].equals("CALL")) {
359 AltosParse.word (words[i++], "VERSION");
360 version = AltosParse.parse_int(words[i++]);
364 parse_legacy(words, i);