]> git.mxchange.org Git - flightgear.git/blob - src/Network/nmea.cxx
d32d6953de844a283617065b8c19cd244f1f87d5
[flightgear.git] / src / Network / nmea.cxx
1 // nmea.cxx -- NMEA protocal class
2 //
3 // Written by Curtis Olson, started November 1999.
4 //
5 // Copyright (C) 1999  Curtis L. Olson - curt@flightgear.org
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 // $Id$
22
23
24 #include <Debug/logstream.hxx>
25 #include <FDM/flight.hxx>
26 #include <Math/fg_geodesy.hxx>
27 #include <Time/fg_time.hxx>
28
29 #include "iochannel.hxx"
30 #include "nmea.hxx"
31
32
33 FGNMEA::FGNMEA() {
34 }
35
36 FGNMEA::~FGNMEA() {
37 }
38
39
40 // calculate the nmea check sum
41 static char calc_nmea_cksum(char *sentence) {
42     unsigned char sum = 0;
43     int i, len;
44
45     // cout << sentence << endl;
46
47     len = strlen(sentence);
48     sum = sentence[0];
49     for ( i = 1; i < len; i++ ) {
50         // cout << sentence[i];
51         sum ^= sentence[i];
52     }
53     // cout << endl;
54
55     // printf("sum = %02x\n", sum);
56     return sum;
57 }
58
59
60 // generate NMEA message
61 bool FGNMEA::gen_message() {
62     // cout << "generating nmea message" << endl;
63
64     char rmc[256], gga[256];
65     char rmc_sum[10], gga_sum[10];
66     char dir;
67     int deg;
68     double min;
69
70     FGTime *t = FGTime::cur_time_params;
71
72     char utc[10];
73     sprintf( utc, "%02d%02d%02d", 
74              t->getGmt()->tm_hour, t->getGmt()->tm_min, t->getGmt()->tm_sec );
75
76     char lat[20];
77     double latd = cur_fdm_state->get_Latitude() * RAD_TO_DEG;
78     if ( latd < 0.0 ) {
79         latd *= -1.0;
80         dir = 'S';
81     } else {
82         dir = 'N';
83     }
84     deg = (int)(latd);
85     min = (latd - (double)deg) * 60.0;
86     sprintf( lat, "%02d%06.3f,%c", abs(deg), min, dir);
87
88     char lon[20];
89     double lond = cur_fdm_state->get_Longitude() * RAD_TO_DEG;
90     if ( lond < 0.0 ) {
91         lond *= -1.0;
92         dir = 'W';
93     } else {
94         dir = 'E';
95     }
96     deg = (int)(lond);
97     min = (lond - (double)deg) * 60.0;
98     sprintf( lon, "%03d%06.3f,%c", abs(deg), min, dir);
99
100     char speed[10];
101     sprintf( speed, "%05.1f", cur_fdm_state->get_V_equiv_kts() );
102
103     char heading[10];
104     sprintf( heading, "%05.1f", cur_fdm_state->get_Psi() * RAD_TO_DEG );
105
106     char altitude_m[10];
107     sprintf( altitude_m, "%02d", 
108              (int)(cur_fdm_state->get_Altitude() * FEET_TO_METER) );
109
110     char altitude_ft[10];
111     sprintf( altitude_ft, "%02d", (int)cur_fdm_state->get_Altitude() );
112
113     char date[10];
114     sprintf( date, "%02d%02d%02d", t->getGmt()->tm_mday, 
115              t->getGmt()->tm_mon+1, t->getGmt()->tm_year );
116
117     // $GPRMC,HHMMSS,A,DDMM.MMM,N,DDDMM.MMM,W,XXX.X,XXX.X,DDMMYY,XXX.X,E*XX
118     sprintf( rmc, "GPRMC,%s,A,%s,%s,%s,%s,%s,0.000,E",
119              utc, lat, lon, speed, heading, date );
120     sprintf( rmc_sum, "%02X", calc_nmea_cksum(rmc) );
121
122     sprintf( gga, "GPGGA,%s,%s,%s,1,,,%s,F,,,,",
123              utc, lat, lon, altitude_ft );
124     sprintf( gga_sum, "%02X", calc_nmea_cksum(gga) );
125
126
127     FG_LOG( FG_IO, FG_DEBUG, rmc );
128     FG_LOG( FG_IO, FG_DEBUG, gga );
129
130     string nmea_sentence;
131
132     // RMC sentence
133     nmea_sentence = "$";
134     nmea_sentence += rmc;
135     nmea_sentence += "*";
136     nmea_sentence += rmc_sum;
137     nmea_sentence += "\n";
138
139     // GGA sentence
140     nmea_sentence += "$";
141     nmea_sentence += gga;
142     nmea_sentence += "*";
143     nmea_sentence += gga_sum;
144     nmea_sentence += "\n";
145
146     cout << nmea_sentence;
147
148     length = nmea_sentence.length();
149     strncpy( buf, nmea_sentence.c_str(), length );
150
151     return true;
152 }
153
154
155 // parse NMEA message.  messages will look something like the
156 // following:
157 //
158 // $GPRMC,163227,A,3321.173,N,11039.855,W,000.1,270.0,171199,0.000,E*61
159 // $GPGGA,163227,3321.173,N,11039.855,W,1,,,3333,F,,,,*0F
160
161 bool FGNMEA::parse_message() {
162     FG_LOG( FG_IO, FG_INFO, "parse nmea message" );
163
164     string msg = buf;
165     msg = msg.substr( 0, length );
166     FG_LOG( FG_IO, FG_INFO, "entire message = " << msg );
167
168     string::size_type begin_line, end_line, begin, end;
169     begin_line = begin = 0;
170
171     // extract out each line
172     end_line = msg.find("\n", begin_line);
173     while ( end_line != string::npos ) {
174         string line = msg.substr(begin_line, end_line - begin_line);
175         begin_line = end_line + 1;
176         FG_LOG( FG_IO, FG_INFO, "  input line = " << line );
177
178         // leading character
179         string start = msg.substr(begin, 1);
180         ++begin;
181         FG_LOG( FG_IO, FG_INFO, "  start = " << start );
182
183         // sentence
184         end = msg.find(",", begin);
185         if ( end == string::npos ) {
186             return false;
187         }
188     
189         string sentence = msg.substr(begin, end - begin);
190         begin = end + 1;
191         FG_LOG( FG_IO, FG_INFO, "  sentence = " << sentence );
192
193         double lon_deg, lon_min, lat_deg, lat_min;
194         double lon, lat, speed, heading, altitude;
195
196         if ( sentence == "GPRMC" ) {
197             // time
198             end = msg.find(",", begin);
199             if ( end == string::npos ) {
200                 return false;
201             }
202     
203             string utc = msg.substr(begin, end - begin);
204             begin = end + 1;
205             FG_LOG( FG_IO, FG_INFO, "  utc = " << utc );
206
207             // junk
208             end = msg.find(",", begin);
209             if ( end == string::npos ) {
210                 return false;
211             }
212     
213             string junk = msg.substr(begin, end - begin);
214             begin = end + 1;
215             FG_LOG( FG_IO, FG_INFO, "  junk = " << junk );
216
217             // lat val
218             end = msg.find(",", begin);
219             if ( end == string::npos ) {
220                 return false;
221             }
222     
223             string lat_str = msg.substr(begin, end - begin);
224             begin = end + 1;
225
226             lat_deg = atof( lat_str.substr(0, 2).c_str() );
227             lat_min = atof( lat_str.substr(2).c_str() );
228
229             // lat dir
230             end = msg.find(",", begin);
231             if ( end == string::npos ) {
232                 return false;
233             }
234     
235             string lat_dir = msg.substr(begin, end - begin);
236             begin = end + 1;
237
238             lat = lat_deg + ( lat_min / 60.0 );
239             if ( lat_dir == "S" ) {
240                 lat *= -1;
241             }
242
243             cur_fdm_state->set_Latitude( lat * DEG_TO_RAD );
244             FG_LOG( FG_IO, FG_INFO, "  lat = " << lat );
245
246             // lon val
247             end = msg.find(",", begin);
248             if ( end == string::npos ) {
249                 return false;
250             }
251     
252             string lon_str = msg.substr(begin, end - begin);
253             begin = end + 1;
254
255             lon_deg = atof( lon_str.substr(0, 3).c_str() );
256             lon_min = atof( lon_str.substr(3).c_str() );
257
258             // lon dir
259             end = msg.find(",", begin);
260             if ( end == string::npos ) {
261                 return false;
262             }
263     
264             string lon_dir = msg.substr(begin, end - begin);
265             begin = end + 1;
266
267             lon = lon_deg + ( lon_min / 60.0 );
268             if ( lon_dir == "W" ) {
269                 lon *= -1;
270             }
271
272             cur_fdm_state->set_Longitude( lon * DEG_TO_RAD );
273             FG_LOG( FG_IO, FG_INFO, "  lon = " << lon );
274
275             double sl_radius, lat_geoc;
276             fgGeodToGeoc( cur_fdm_state->get_Latitude(), 
277                           cur_fdm_state->get_Altitude(), 
278                           &sl_radius, &lat_geoc );
279             cur_fdm_state->set_Geocentric_Position( lat_geoc, 
280                            cur_fdm_state->get_Longitude(), 
281                            sl_radius + cur_fdm_state->get_Altitude() );
282
283             // speed
284             end = msg.find(",", begin);
285             if ( end == string::npos ) {
286                 return false;
287             }
288     
289             string speed_str = msg.substr(begin, end - begin);
290             begin = end + 1;
291             speed = atof( speed_str.c_str() );
292             cur_fdm_state->set_V_equiv_kts( speed );
293             cur_fdm_state->set_V_ground_speed( speed );
294             FG_LOG( FG_IO, FG_INFO, "  speed = " << speed );
295
296             // heading
297             end = msg.find(",", begin);
298             if ( end == string::npos ) {
299                 return false;
300             }
301     
302             string hdg_str = msg.substr(begin, end - begin);
303             begin = end + 1;
304             heading = atof( hdg_str.c_str() );
305             cur_fdm_state->set_Euler_Angles( cur_fdm_state->get_Phi(), 
306                                              cur_fdm_state->get_Theta(), 
307                                              heading * DEG_TO_RAD );
308             FG_LOG( FG_IO, FG_INFO, "  heading = " << heading );
309         } else if ( sentence == "GPGGA" ) {
310             // time
311             end = msg.find(",", begin);
312             if ( end == string::npos ) {
313                 return false;
314             }
315     
316             string utc = msg.substr(begin, end - begin);
317             begin = end + 1;
318             FG_LOG( FG_IO, FG_INFO, "  utc = " << utc );
319
320             // lat val
321             end = msg.find(",", begin);
322             if ( end == string::npos ) {
323                 return false;
324             }
325     
326             string lat_str = msg.substr(begin, end - begin);
327             begin = end + 1;
328
329             lat_deg = atof( lat_str.substr(0, 2).c_str() );
330             lat_min = atof( lat_str.substr(2).c_str() );
331
332             // lat dir
333             end = msg.find(",", begin);
334             if ( end == string::npos ) {
335                 return false;
336             }
337     
338             string lat_dir = msg.substr(begin, end - begin);
339             begin = end + 1;
340
341             lat = lat_deg + ( lat_min / 60.0 );
342             if ( lat_dir == "S" ) {
343                 lat *= -1;
344             }
345
346             // cur_fdm_state->set_Latitude( lat * DEG_TO_RAD );
347             FG_LOG( FG_IO, FG_INFO, "  lat = " << lat );
348
349             // lon val
350             end = msg.find(",", begin);
351             if ( end == string::npos ) {
352                 return false;
353             }
354     
355             string lon_str = msg.substr(begin, end - begin);
356             begin = end + 1;
357
358             lon_deg = atof( lon_str.substr(0, 3).c_str() );
359             lon_min = atof( lon_str.substr(3).c_str() );
360
361             // lon dir
362             end = msg.find(",", begin);
363             if ( end == string::npos ) {
364                 return false;
365             }
366     
367             string lon_dir = msg.substr(begin, end - begin);
368             begin = end + 1;
369
370             lon = lon_deg + ( lon_min / 60.0 );
371             if ( lon_dir == "W" ) {
372                 lon *= -1;
373             }
374
375             // cur_fdm_state->set_Longitude( lon * DEG_TO_RAD );
376             FG_LOG( FG_IO, FG_INFO, "  lon = " << lon );
377
378             // junk
379             end = msg.find(",", begin);
380             if ( end == string::npos ) {
381                 return false;
382             }
383     
384             string junk = msg.substr(begin, end - begin);
385             begin = end + 1;
386             FG_LOG( FG_IO, FG_INFO, "  junk = " << junk );
387
388             // junk
389             end = msg.find(",", begin);
390             if ( end == string::npos ) {
391                 return false;
392             }
393     
394             junk = msg.substr(begin, end - begin);
395             begin = end + 1;
396             FG_LOG( FG_IO, FG_INFO, "  junk = " << junk );
397
398             // junk
399             end = msg.find(",", begin);
400             if ( end == string::npos ) {
401                 return false;
402             }
403     
404             junk = msg.substr(begin, end - begin);
405             begin = end + 1;
406             FG_LOG( FG_IO, FG_INFO, "  junk = " << junk );
407
408             // altitude
409             end = msg.find(",", begin);
410             if ( end == string::npos ) {
411                 return false;
412             }
413     
414             string alt_str = msg.substr(begin, end - begin);
415             altitude = atof( alt_str.c_str() );
416             begin = end + 1;
417
418             // altitude units
419             end = msg.find(",", begin);
420             if ( end == string::npos ) {
421                 return false;
422             }
423     
424             string alt_units = msg.substr(begin, end - begin);
425             begin = end + 1;
426
427             if ( alt_units != "F" ) {
428                 altitude *= METER_TO_FEET;
429             }
430
431             cur_fdm_state->set_Altitude( altitude );
432     
433             FG_LOG( FG_IO, FG_INFO, " altitude  = " << altitude );
434
435         }
436
437         // printf("%.8f %.8f\n", lon, lat);
438
439         begin = begin_line;
440         end_line = msg.find("\n", begin_line);
441     }
442
443     return true;
444 }
445
446
447 // open hailing frequencies
448 bool FGNMEA::open() {
449     if ( is_enabled() ) {
450         FG_LOG( FG_IO, FG_ALERT, "This shouldn't happen, but the channel " 
451                 << "is already in use, ignoring" );
452         return false;
453     }
454
455     FGIOChannel *io = get_io_channel();
456
457     if ( ! io->open( get_direction() ) ) {
458         FG_LOG( FG_IO, FG_ALERT, "Error opening channel communication layer." );
459         return false;
460     }
461
462     set_enabled( true );
463
464     return true;
465 }
466
467
468 // process work for this port
469 bool FGNMEA::process() {
470     FGIOChannel *io = get_io_channel();
471
472     if ( get_direction() == out ) {
473         gen_message();
474         if ( ! io->write( buf, length ) ) {
475             FG_LOG( FG_IO, FG_ALERT, "Error writing data." );
476             return false;
477         }
478     } else if ( get_direction() == in ) {
479         if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
480             parse_message();
481         } else {
482             FG_LOG( FG_IO, FG_ALERT, "Error reading data." );
483             return false;
484         }
485         if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
486             parse_message();
487         } else {
488             FG_LOG( FG_IO, FG_ALERT, "Error reading data." );
489             return false;
490         }
491     }
492
493     return true;
494 }
495
496
497 // close the channel
498 bool FGNMEA::close() {
499     FGIOChannel *io = get_io_channel();
500
501     set_enabled( false );
502
503     if ( ! io->close() ) {
504         return false;
505     }
506
507     return true;
508 }