]> git.mxchange.org Git - flightgear.git/blob - src/Network/nmea.cxx
Merge branch 'topics/mainloop' into next
[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 - http://www.flightgear.org/~curt
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <simgear/debug/logstream.hxx>
28 #include <simgear/math/sg_geodesy.hxx>
29 #include <simgear/io/iochannel.hxx>
30 #include <simgear/timing/sg_time.hxx>
31
32 #include <FDM/flightProperties.hxx>
33 #include <Main/fg_props.hxx>
34 #include <Main/globals.hxx>
35
36 #include "nmea.hxx"
37
38 FGNMEA::FGNMEA() {
39   fdm = new FlightProperties();
40 }
41
42 FGNMEA::~FGNMEA() {
43   delete fdm;
44 }
45
46
47 // calculate the nmea check sum
48 static char calc_nmea_cksum(char *sentence) {
49     unsigned char sum = 0;
50     int i, len;
51
52     // cout << sentence << endl;
53
54     len = strlen(sentence);
55     sum = sentence[0];
56     for ( i = 1; i < len; i++ ) {
57         // cout << sentence[i];
58         sum ^= sentence[i];
59     }
60     // cout << endl;
61
62     // printf("sum = %02x\n", sum);
63     return sum;
64 }
65
66
67 // generate NMEA message
68 bool FGNMEA::gen_message() {
69     // cout << "generating nmea message" << endl;
70
71     char rmc[256], gga[256], gsa[256];
72     char rmc_sum[10], gga_sum[10];
73     char dir;
74     int deg;
75     double min;
76
77     SGTime *t = globals->get_time_params();
78
79     char utc[10];
80     sprintf( utc, "%02d%02d%02d", 
81              t->getGmt()->tm_hour, t->getGmt()->tm_min, t->getGmt()->tm_sec );
82
83     char gga_lat[20], rmc_lat[20];
84     double latd = fdm->get_Latitude() * SGD_RADIANS_TO_DEGREES;
85     if ( latd < 0.0 ) {
86         latd = -latd;
87         dir = 'S';
88     } else {
89         dir = 'N';
90     }
91     deg = (int)(latd);
92     min = (latd - (double)deg) * 60.0;
93     sprintf( gga_lat, "%02d%07.4f,%c", abs(deg), min, dir);
94     sprintf( rmc_lat, "%02d%07.4f,%c", abs(deg), min, dir);
95
96     char gga_lon[20], rmc_lon[20];
97     double lond = fdm->get_Longitude() * SGD_RADIANS_TO_DEGREES;
98     if ( lond < 0.0 ) {
99         lond = -lond;
100         dir = 'W';
101     } else {
102         dir = 'E';
103     }
104     deg = (int)(lond);
105     min = (lond - (double)deg) * 60.0;
106     sprintf( gga_lon, "%03d%07.4f,%c", abs(deg), min, dir);
107     sprintf( rmc_lon, "%03d%07.4f,%c", abs(deg), min, dir);
108
109     double vn = fgGetDouble( "/velocities/speed-north-fps" );
110     double ve = fgGetDouble( "/velocities/speed-east-fps" );
111     double fps = sqrt( vn*vn + ve*ve );
112     double mps = fps * SG_FEET_TO_METER;
113     double kts = mps * SG_METER_TO_NM * 3600;
114     char speed[10];
115     sprintf( speed, "%.1f", kts );
116
117     double hdg_true = atan2( ve, vn ) * SGD_RADIANS_TO_DEGREES;
118     if ( hdg_true < 0 ) {
119       hdg_true += 360.0;
120     }
121     char heading[10];
122     sprintf( heading, "%.1f", hdg_true );
123
124     char altitude_m[10];
125     sprintf( altitude_m, "%.1f", 
126              fdm->get_Altitude() * SG_FEET_TO_METER );
127
128     char date[10];
129     int year = t->getGmt()->tm_year;
130     while ( year >= 100 ) { year -= 100; }
131     sprintf( date, "%02d%02d%02d", t->getGmt()->tm_mday, 
132              t->getGmt()->tm_mon+1, year );
133
134     char magvar[10];
135     float magdeg = fgGetDouble( "/environment/magnetic-variation-deg" );
136     if ( magdeg < 0.0 ) {
137         magdeg = -magdeg;
138         dir = 'W';
139     } else {
140         dir = 'E';
141     }
142     sprintf( magvar, "%.1f,%c", magdeg, dir );
143  
144     // $GPRMC,HHMMSS,A,DDMM.MMMM,N,DDDMM.MMMM,W,XXX.X,XXX.X,DDMMYY,XXX.X,E,A*XX
145     sprintf( rmc, "GPRMC,%s,A,%s,%s,%s,%s,%s,%s,A",
146              utc, rmc_lat, rmc_lon, speed, heading, date, magvar );
147     sprintf( rmc_sum, "%02X", calc_nmea_cksum(rmc) );
148
149     // $GPGGA,HHMMSS,DDMM.MMMM,N,DDDMM.MMMM,W,1,NN,H.H,AAAA.A,M,GG.G,M,,*XX
150     sprintf( gga, "GPGGA,%s,%s,%s,1,08,0.9,%s,M,0.0,M,,",
151              utc, gga_lat, gga_lon, altitude_m );
152     sprintf( gga_sum, "%02X", calc_nmea_cksum(gga) );
153     sprintf( gsa, "%s",
154              "$GPGSA,A,3,01,02,03,,05,,07,,09,,11,12,0.9,0.9,2.0*38" );
155
156     SG_LOG( SG_IO, SG_DEBUG, rmc );
157     SG_LOG( SG_IO, SG_DEBUG, gga );
158     SG_LOG( SG_IO, SG_DEBUG, gsa );
159
160     string nmea_sentence;
161
162     // RMC sentence
163     nmea_sentence = "$";
164     nmea_sentence += rmc;
165     nmea_sentence += "*";
166     nmea_sentence += rmc_sum;
167     nmea_sentence += "\n";
168
169     // GGA sentence
170     nmea_sentence += "$";
171     nmea_sentence += gga;
172     nmea_sentence += "*";
173     nmea_sentence += gga_sum;
174     nmea_sentence += "\n";
175
176     // GSA sentence (totally faked)
177     nmea_sentence += gsa;
178     nmea_sentence += "\n";
179
180     // cout << nmea_sentence;
181
182     length = nmea_sentence.length();
183     strncpy( buf, nmea_sentence.c_str(), length );
184
185     return true;
186 }
187
188
189 // parse NMEA message.  messages will look something like the
190 // following:
191 //
192 // $GPRMC,163227,A,3321.173,N,11039.855,W,000.1,270.0,171199,0.000,E*61
193 // $GPGGA,163227,3321.173,N,11039.855,W,1,,,3333,F,,,,*0F
194
195 bool FGNMEA::parse_message() {
196     SG_LOG( SG_IO, SG_INFO, "parse nmea message" );
197
198     string msg = buf;
199     msg = msg.substr( 0, length );
200     SG_LOG( SG_IO, SG_INFO, "entire message = " << msg );
201
202     string::size_type begin_line, end_line, begin, end;
203     begin_line = begin = 0;
204
205     // extract out each line
206     end_line = msg.find("\n", begin_line);
207     while ( end_line != string::npos ) {
208         string line = msg.substr(begin_line, end_line - begin_line);
209         begin_line = end_line + 1;
210         SG_LOG( SG_IO, SG_INFO, "  input line = " << line );
211
212         // leading character
213         string start = msg.substr(begin, 1);
214         ++begin;
215         SG_LOG( SG_IO, SG_INFO, "  start = " << start );
216
217         // sentence
218         end = msg.find(",", begin);
219         if ( end == string::npos ) {
220             return false;
221         }
222     
223         string sentence = msg.substr(begin, end - begin);
224         begin = end + 1;
225         SG_LOG( SG_IO, SG_INFO, "  sentence = " << sentence );
226
227         double lon_deg, lon_min, lat_deg, lat_min;
228         double lon, lat, speed, heading, altitude;
229
230         if ( sentence == "GPRMC" ) {
231             // time
232             end = msg.find(",", begin);
233             if ( end == string::npos ) {
234                 return false;
235             }
236     
237             string utc = msg.substr(begin, end - begin);
238             begin = end + 1;
239             SG_LOG( SG_IO, SG_INFO, "  utc = " << utc );
240
241             // junk
242             end = msg.find(",", begin);
243             if ( end == string::npos ) {
244                 return false;
245             }
246     
247             string junk = msg.substr(begin, end - begin);
248             begin = end + 1;
249             SG_LOG( SG_IO, SG_INFO, "  junk = " << junk );
250
251             // lat val
252             end = msg.find(",", begin);
253             if ( end == string::npos ) {
254                 return false;
255             }
256     
257             string lat_str = msg.substr(begin, end - begin);
258             begin = end + 1;
259
260             lat_deg = atof( lat_str.substr(0, 2).c_str() );
261             lat_min = atof( lat_str.substr(2).c_str() );
262
263             // lat dir
264             end = msg.find(",", begin);
265             if ( end == string::npos ) {
266                 return false;
267             }
268     
269             string lat_dir = msg.substr(begin, end - begin);
270             begin = end + 1;
271
272             lat = lat_deg + ( lat_min / 60.0 );
273             if ( lat_dir == "S" ) {
274                 lat *= -1;
275             }
276
277             fdm->set_Latitude( lat * SGD_DEGREES_TO_RADIANS );
278             SG_LOG( SG_IO, SG_INFO, "  lat = " << lat );
279
280             // lon val
281             end = msg.find(",", begin);
282             if ( end == string::npos ) {
283                 return false;
284             }
285     
286             string lon_str = msg.substr(begin, end - begin);
287             begin = end + 1;
288
289             lon_deg = atof( lon_str.substr(0, 3).c_str() );
290             lon_min = atof( lon_str.substr(3).c_str() );
291
292             // lon dir
293             end = msg.find(",", begin);
294             if ( end == string::npos ) {
295                 return false;
296             }
297     
298             string lon_dir = msg.substr(begin, end - begin);
299             begin = end + 1;
300
301             lon = lon_deg + ( lon_min / 60.0 );
302             if ( lon_dir == "W" ) {
303                 lon *= -1;
304             }
305
306             fdm->set_Longitude( lon * SGD_DEGREES_TO_RADIANS );
307             SG_LOG( SG_IO, SG_INFO, "  lon = " << lon );
308
309 #if 0
310             double sl_radius, lat_geoc;
311             sgGeodToGeoc( fdm->get_Latitude(), 
312                           fdm->get_Altitude(), 
313                           &sl_radius, &lat_geoc );
314             fdm->set_Geocentric_Position( lat_geoc, 
315                            fdm->get_Longitude(), 
316                            sl_radius + fdm->get_Altitude() );
317 #endif
318
319             // speed
320             end = msg.find(",", begin);
321             if ( end == string::npos ) {
322                 return false;
323             }
324     
325             string speed_str = msg.substr(begin, end - begin);
326             begin = end + 1;
327             speed = atof( speed_str.c_str() );
328             fdm->set_V_calibrated_kts( speed );
329             // fdm->set_V_ground_speed( speed );
330             SG_LOG( SG_IO, SG_INFO, "  speed = " << speed );
331
332             // heading
333             end = msg.find(",", begin);
334             if ( end == string::npos ) {
335                 return false;
336             }
337     
338             string hdg_str = msg.substr(begin, end - begin);
339             begin = end + 1;
340             heading = atof( hdg_str.c_str() );
341             fdm->set_Euler_Angles( fdm->get_Phi(), 
342                                              fdm->get_Theta(), 
343                                              heading * SGD_DEGREES_TO_RADIANS );
344             SG_LOG( SG_IO, SG_INFO, "  heading = " << heading );
345         } else if ( sentence == "GPGGA" ) {
346             // time
347             end = msg.find(",", begin);
348             if ( end == string::npos ) {
349                 return false;
350             }
351     
352             string utc = msg.substr(begin, end - begin);
353             begin = end + 1;
354             SG_LOG( SG_IO, SG_INFO, "  utc = " << utc );
355
356             // lat val
357             end = msg.find(",", begin);
358             if ( end == string::npos ) {
359                 return false;
360             }
361     
362             string lat_str = msg.substr(begin, end - begin);
363             begin = end + 1;
364
365             lat_deg = atof( lat_str.substr(0, 2).c_str() );
366             lat_min = atof( lat_str.substr(2).c_str() );
367
368             // lat dir
369             end = msg.find(",", begin);
370             if ( end == string::npos ) {
371                 return false;
372             }
373     
374             string lat_dir = msg.substr(begin, end - begin);
375             begin = end + 1;
376
377             lat = lat_deg + ( lat_min / 60.0 );
378             if ( lat_dir == "S" ) {
379                 lat *= -1;
380             }
381
382             // fdm->set_Latitude( lat * SGD_DEGREES_TO_RADIANS );
383             SG_LOG( SG_IO, SG_INFO, "  lat = " << lat );
384
385             // lon val
386             end = msg.find(",", begin);
387             if ( end == string::npos ) {
388                 return false;
389             }
390     
391             string lon_str = msg.substr(begin, end - begin);
392             begin = end + 1;
393
394             lon_deg = atof( lon_str.substr(0, 3).c_str() );
395             lon_min = atof( lon_str.substr(3).c_str() );
396
397             // lon dir
398             end = msg.find(",", begin);
399             if ( end == string::npos ) {
400                 return false;
401             }
402     
403             string lon_dir = msg.substr(begin, end - begin);
404             begin = end + 1;
405
406             lon = lon_deg + ( lon_min / 60.0 );
407             if ( lon_dir == "W" ) {
408                 lon *= -1;
409             }
410
411             // fdm->set_Longitude( lon * SGD_DEGREES_TO_RADIANS );
412             SG_LOG( SG_IO, SG_INFO, "  lon = " << lon );
413
414             // junk
415             end = msg.find(",", begin);
416             if ( end == string::npos ) {
417                 return false;
418             }
419     
420             string junk = msg.substr(begin, end - begin);
421             begin = end + 1;
422             SG_LOG( SG_IO, SG_INFO, "  junk = " << junk );
423
424             // junk
425             end = msg.find(",", begin);
426             if ( end == string::npos ) {
427                 return false;
428             }
429     
430             junk = msg.substr(begin, end - begin);
431             begin = end + 1;
432             SG_LOG( SG_IO, SG_INFO, "  junk = " << junk );
433
434             // junk
435             end = msg.find(",", begin);
436             if ( end == string::npos ) {
437                 return false;
438             }
439     
440             junk = msg.substr(begin, end - begin);
441             begin = end + 1;
442             SG_LOG( SG_IO, SG_INFO, "  junk = " << junk );
443
444             // altitude
445             end = msg.find(",", begin);
446             if ( end == string::npos ) {
447                 return false;
448             }
449     
450             string alt_str = msg.substr(begin, end - begin);
451             altitude = atof( alt_str.c_str() );
452             begin = end + 1;
453
454             // altitude units
455             end = msg.find(",", begin);
456             if ( end == string::npos ) {
457                 return false;
458             }
459     
460             string alt_units = msg.substr(begin, end - begin);
461             begin = end + 1;
462
463             if ( alt_units != "F" ) {
464                 altitude *= SG_METER_TO_FEET;
465             }
466
467             fdm->set_Altitude( altitude );
468     
469             SG_LOG( SG_IO, SG_INFO, " altitude  = " << altitude );
470
471         }
472
473         // printf("%.8f %.8f\n", lon, lat);
474
475         begin = begin_line;
476         end_line = msg.find("\n", begin_line);
477     }
478
479     return true;
480 }
481
482
483 // open hailing frequencies
484 bool FGNMEA::open() {
485     if ( is_enabled() ) {
486         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel " 
487                 << "is already in use, ignoring" );
488         return false;
489     }
490
491     SGIOChannel *io = get_io_channel();
492
493     if ( ! io->open( get_direction() ) ) {
494         SG_LOG( SG_IO, SG_ALERT, "Error opening channel communication layer." );
495         return false;
496     }
497
498     set_enabled( true );
499
500     return true;
501 }
502
503
504 // process work for this port
505 bool FGNMEA::process() {
506     SGIOChannel *io = get_io_channel();
507
508     if ( get_direction() == SG_IO_OUT ) {
509         gen_message();
510         if ( ! io->write( buf, length ) ) {
511             SG_LOG( SG_IO, SG_WARN, "Error writing data." );
512             return false;
513         }
514     } else if ( get_direction() == SG_IO_IN ) {
515         if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
516             parse_message();
517         } else {
518             SG_LOG( SG_IO, SG_WARN, "Error reading data." );
519             return false;
520         }
521         if ( (length = io->readline( buf, FG_MAX_MSG_SIZE )) > 0 ) {
522             parse_message();
523         } else {
524             SG_LOG( SG_IO, SG_WARN, "Error reading data." );
525             return false;
526         }
527     }
528
529     return true;
530 }
531
532
533 // close the channel
534 bool FGNMEA::close() {
535     SGIOChannel *io = get_io_channel();
536
537     set_enabled( false );
538
539     if ( ! io->close() ) {
540         return false;
541     }
542
543     return true;
544 }