]> git.mxchange.org Git - flightgear.git/blob - Main/fg_serial.cxx
cfa7d6868a37430ce2bb1445999bfb481c1a03a5
[flightgear.git] / Main / fg_serial.cxx
1 // fg_serial.cxx -- higher level serial port management routines
2 //
3 // Written by Curtis Olson, started November 1998.
4 //
5 // Copyright (C) 1998  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 // (Log is kept at end of this file)
23
24
25 #include <Include/compiler.h>
26
27 #ifdef FG_HAVE_STD_INCLUDES
28 #  include <cstdlib>    // atoi()
29 #else
30 #  include <stdlib.h>   // atoi()
31 #endif
32
33 #include STL_STRING
34 #include STL_IOSTREAM                                           
35 #include <vector>                                                               
36 #include "Include/fg_stl_config.h"                                              
37
38 FG_USING_NAMESPACE(std);
39
40 #include <Aircraft/aircraft.hxx>
41 #include <Debug/logstream.hxx>
42 #include <Include/fg_constants.h>
43 #include <Serial/serial.hxx>
44 #include <Time/fg_time.hxx>
45
46 #include "options.hxx"
47
48 #include "fg_serial.hxx"
49
50
51 // support an arbitrary number of serial channels.  Each channel can
52 // be assigned to an arbitrary port.  Bi-directional communication is
53 // supported by the underlying layer, but probably will never be
54 // needed by FGFS?
55
56 typedef vector < fgIOCHANNEL > io_container;
57 typedef io_container::iterator io_iterator;
58 typedef io_container::const_iterator const_io_iterator;
59
60 // define the four channels
61 io_container port_list;
62
63
64 fgIOCHANNEL::fgIOCHANNEL() :
65     kind( FG_SERIAL_DISABLED ),
66     valid_config( false )
67 {
68 }
69
70
71 fgIOCHANNEL::~fgIOCHANNEL() {
72 }
73
74
75 // configure a port based on the config string
76 static fgIOCHANNEL parse_port_config( const string& config )
77 {
78     fgIOCHANNEL p;
79
80     string::size_type begin, end;
81
82     begin = 0;
83
84     FG_LOG( FG_SERIAL, FG_INFO, "Parse serial port config: " << config );
85
86     // device name
87     end = config.find(",", begin);
88     if ( end == string::npos ) {
89         return p;
90     }
91     
92     p.device = config.substr(begin, end - begin);
93     begin = end + 1;
94     FG_LOG( FG_SERIAL, FG_INFO, "  device = " << p.device );
95
96     // format
97     end = config.find(",", begin);
98     if ( end == string::npos ) {
99         return p;
100     }
101     
102     p.format = config.substr(begin, end - begin);
103     begin = end + 1;
104     FG_LOG( FG_SERIAL, FG_INFO, "  format = " << p.format );
105
106     // baud
107     end = config.find(",", begin);
108     if ( end == string::npos ) {
109         return p;
110     }
111     
112     p.baud = config.substr(begin, end - begin);
113     begin = end + 1;
114     FG_LOG( FG_SERIAL, FG_INFO, "  baud = " << p.baud );
115
116     // direction
117     p.direction = config.substr(begin);
118     FG_LOG( FG_SERIAL, FG_INFO, "  direction = " << p.direction );
119
120     p.valid_config = true;
121
122     return p;
123 }
124
125
126 // configure a port based on the config info
127 static bool config_port( fgIOCHANNEL &p )
128 {
129     if ( p.port.is_enabled() ) {
130         FG_LOG( FG_SERIAL, FG_ALERT, "This shouldn't happen, but the port " 
131                 << "is already in use, ignoring" );
132         return false;
133     }
134
135     if ( ! p.port.open_port( p.device ) ) {
136         FG_LOG( FG_SERIAL, FG_ALERT, "Error opening device: " << p.device );
137         return false;
138     }
139
140     // cout << "fd = " << p.port.fd << endl;
141
142     if ( ! p.port.set_baud( atoi( p.baud.c_str() ) ) ) {
143         FG_LOG( FG_SERIAL, FG_ALERT, "Error setting baud: " << p.baud );
144         return false;
145     }
146
147     if ( p.format == "nmea" ) {
148         if ( p.direction == "out" ) {
149             p.kind = fgIOCHANNEL::FG_SERIAL_NMEA_OUT;
150         } else if ( p.direction == "in" ) {
151             p.kind = fgIOCHANNEL::FG_SERIAL_NMEA_IN;
152         } else {
153             FG_LOG( FG_SERIAL, FG_ALERT, "Unknown direction" );
154             return false;
155         }
156     } else if ( p.format == "garmin" ) {
157         if ( p.direction == "out" ) {
158             p.kind = fgIOCHANNEL::FG_SERIAL_GARMIN_OUT;
159         } else if ( p.direction == "in" ) {
160             p.kind = fgIOCHANNEL::FG_SERIAL_GARMIN_IN;
161         } else {
162             FG_LOG( FG_SERIAL, FG_ALERT, "Unknown direction" );
163             return false;
164         }
165     } else if ( p.format == "fgfs" ) {
166         if ( p.direction == "out" ) {
167             p.kind = fgIOCHANNEL::FG_SERIAL_FGFS_OUT;
168         } else if ( p.direction == "in" ) {
169             p.kind = fgIOCHANNEL::FG_SERIAL_FGFS_IN;
170         } else {
171             FG_LOG( FG_SERIAL, FG_ALERT, "Unknown direction" );
172             return false;
173         }
174     } else {
175         FG_LOG( FG_SERIAL, FG_ALERT, "Unknown format" );
176         return false;
177     }
178
179     return true;
180 }
181
182
183 // step through the port config streams (from fgOPTIONS) and setup
184 // serial port channels for each
185 void fgSerialInit() {
186     fgIOCHANNEL port;
187     bool result;
188     str_container port_options_list = current_options.get_port_options_list();
189
190     // we could almost do this in a single step except pushing a valid
191     // port onto the port list copies the structure and destroys the
192     // original, which closes the port and frees up the fd ... doh!!!
193
194     // parse the configuration strings and store the results in stub
195     // fgIOCHANNEL structures
196     const_str_iterator current_str = port_options_list.begin();
197     const_str_iterator last_str = port_options_list.end();
198     for ( ; current_str != last_str; ++current_str ) {
199         port = parse_port_config( *current_str );
200         if ( port.valid_config ) {
201             result = config_port( port );
202             if ( result ) {
203                 port_list.push_back( port );
204             }
205         }
206     }
207 }
208
209
210 char calc_nmea_cksum(char *sentence) {
211     unsigned char sum = 0;
212     int i, len;
213
214     // printf("%s\n", sentence);
215
216     len = strlen(sentence);
217     sum = sentence[0];
218     for ( i = 1; i < len; i++ ) {
219         // printf("%c", sentence[i]);
220         sum ^= sentence[i];
221     }
222     // printf("\n");
223
224     // printf("sum = %02x\n", sum);
225     return sum;
226 }
227
228
229 static void send_nmea_out( fgIOCHANNEL& p ) {
230     char rmc[256], gga[256];
231     char rmc_sum[10], gga_sum[10];
232     char dir;
233     int deg;
234     double min;
235     FGState *f;
236     fgTIME *t;
237
238     // run once every two seconds
239     if ( p.last_time == cur_time_params.cur_time ) {
240         return;
241     }
242     p.last_time = cur_time_params.cur_time;
243     if ( cur_time_params.cur_time % 2 != 0 ) {
244         return;
245     }
246
247     f = current_aircraft.fdm_state;
248     t = &cur_time_params;
249
250     char utc[10];
251     sprintf( utc, "%02d%02d%02d", 
252              t->gmt->tm_hour, t->gmt->tm_min, t->gmt->tm_sec );
253
254     char lat[20];
255     double latd = f->get_Latitude() * RAD_TO_DEG;
256     if ( latd < 0.0 ) {
257         latd *= -1.0;
258         dir = 'S';
259     } else {
260         dir = 'N';
261     }
262     deg = (int)(latd);
263     min = (latd - (double)deg) * 60.0;
264     sprintf( lat, "%02d%06.3f,%c", abs(deg), min, dir);
265
266     char lon[20];
267     double lond = f->get_Longitude() * RAD_TO_DEG;
268     if ( lond < 0.0 ) {
269         lond *= -1.0;
270         dir = 'W';
271     } else {
272         dir = 'E';
273     }
274     deg = (int)(lond);
275     min = (lond - (double)deg) * 60.0;
276     sprintf( lon, "%03d%06.3f,%c", abs(deg), min, dir);
277
278     char speed[10];
279     sprintf( speed, "%05.1f", f->get_V_equiv_kts() );
280
281     char heading[10];
282     sprintf( heading, "%05.1f", f->get_Psi() * RAD_TO_DEG );
283
284     char altitude_m[10];
285     sprintf( altitude_m, "%02d", (int)(f->get_Altitude() * FEET_TO_METER) );
286
287     char altitude_ft[10];
288     sprintf( altitude_ft, "%02d", (int)f->get_Altitude() );
289
290     char date[10];
291     sprintf( date, "%02d%02d%02d", 
292              t->gmt->tm_mday, t->gmt->tm_mon+1, t->gmt->tm_year );
293
294     // $GPRMC,HHMMSS,A,DDMM.MMM,N,DDDMM.MMM,W,XXX.X,XXX.X,DDMMYY,XXX.X,E*XX
295     sprintf( rmc, "GPRMC,%s,A,%s,%s,%s,%s,%s,0.000,E",
296              utc, lat, lon, speed, heading, date );
297     sprintf( rmc_sum, "%02X", 0 /*calc_nmea_cksum(rmc)*/ );
298
299     sprintf( gga, "GPGGA,%s,%s,%s,1,,,%s,F,,,,",
300              utc, lat, lon, altitude_ft );
301     sprintf( gga_sum, "%02X", 0 /*calc_nmea_cksum(gga)*/ );
302
303
304     FG_LOG( FG_SERIAL, FG_DEBUG, rmc );
305     FG_LOG( FG_SERIAL, FG_DEBUG, gga );
306
307     // RMC sentence
308     string rmc_sentence = "$";
309     rmc_sentence += rmc;
310     rmc_sentence += "*";
311     rmc_sentence += rmc_sum;
312     rmc_sentence += "\n";
313     p.port.write_port(rmc_sentence);
314     cout << rmc_sentence;
315
316     // GGA sentence
317     string gga_sentence = "$";
318     gga_sentence += gga;
319     gga_sentence += "*";
320     gga_sentence += gga_sum;
321     gga_sentence += "\n";
322     p.port.write_port(gga_sentence);
323     cout << gga_sentence;
324 }
325
326 static void read_nmea_in( fgIOCHANNEL& p ) {
327 }
328
329 static void send_garmin_out( fgIOCHANNEL& p ) {
330     char rmc[256], rmz[256];
331     char dir;
332     int deg;
333     double min;
334     FGState *f;
335     fgTIME *t;
336
337     // run once per second
338     if ( p.last_time == cur_time_params.cur_time ) {
339         return;
340     }
341     p.last_time = cur_time_params.cur_time;
342     if ( cur_time_params.cur_time % 2 != 0 ) {
343         return;
344     }
345     
346     f = current_aircraft.fdm_state;
347     t = &cur_time_params;
348
349     char utc[10];
350     sprintf( utc, "%02d%02d%02d", 
351              t->gmt->tm_hour, t->gmt->tm_min, t->gmt->tm_sec );
352
353     char lat[20];
354     double latd = f->get_Latitude() * RAD_TO_DEG;
355     if ( latd < 0.0 ) {
356         latd *= -1.0;
357         dir = 'S';
358     } else {
359         dir = 'N';
360     }
361     deg = (int)(latd);
362     min = (latd - (double)deg) * 60.0;
363     sprintf( lat, "%02d%06.3f,%c", abs(deg), min, dir);
364
365     char lon[20];
366     double lond = f->get_Longitude() * RAD_TO_DEG;
367     if ( lond < 0.0 ) {
368         lond *= -1.0;
369         dir = 'W';
370     } else {
371         dir = 'E';
372     }
373     deg = (int)(lond);
374     min = (lond - (double)deg) * 60.0;
375     sprintf( lon, "%03d%06.3f,%c", abs(deg), min, dir);
376
377     char speed[10];
378     sprintf( speed, "%05.1f", f->get_V_equiv_kts() );
379
380     char heading[10];
381     sprintf( heading, "%05.1f", f->get_Psi() * RAD_TO_DEG );
382
383     char altitude_m[10];
384     sprintf( altitude_m, "%02d", (int)(f->get_Altitude() * FEET_TO_METER) );
385
386     char altitude_ft[10];
387     sprintf( altitude_ft, "%02d", (int)f->get_Altitude() );
388
389     char date[10];
390     sprintf( date, "%02d%02d%02d", 
391              t->gmt->tm_mday, t->gmt->tm_mon+1, t->gmt->tm_year );
392
393     // $GPRMC,HHMMSS,A,DDMM.MMM,N,DDDMM.MMM,W,XXX.X,XXX.X,DDMMYY,XXX.X,E*XX
394     sprintf( rmc, "$GPRMC,%s,A,%s,%s,%s,%s,%s,000.0,E*00\r\n",
395              utc, lat, lon, speed, heading, date );
396
397     // sprintf( gga, "$GPGGA,%s,%s,%s,1,04,0.0,%s,M,00.0,M,,*00\r\n",
398     //          utc, lat, lon, altitude_m );
399
400     sprintf( rmz, "$PGRMZ,%s,f,3*00\r\n", altitude_ft );
401
402     FG_LOG( FG_SERIAL, FG_DEBUG, rmc );
403     FG_LOG( FG_SERIAL, FG_DEBUG, rmz );
404
405     // RMC sentence
406     p.port.write_port(rmc);
407     cout << rmc;
408
409     // RMZ sentence (garmin proprietary)
410     p.port.write_port(rmz);
411     cout << rmz;
412 }
413
414 static void read_garmin_in( fgIOCHANNEL& p ) {
415 }
416
417 static void send_fgfs_out( fgIOCHANNEL& p ) {
418 }
419
420 static void read_fgfs_in( fgIOCHANNEL& p ) {
421 }
422
423
424 // one more level of indirection ...
425 static void process_port( fgIOCHANNEL& p ) {
426     if ( p.kind == fgIOCHANNEL::FG_SERIAL_NMEA_OUT ) {
427         send_nmea_out(p);
428     } else if ( p.kind == fgIOCHANNEL::FG_SERIAL_NMEA_IN ) {
429         read_nmea_in(p);
430     } else if ( p.kind == fgIOCHANNEL::FG_SERIAL_GARMIN_OUT ) {
431         send_garmin_out(p);
432     } else if ( p.kind == fgIOCHANNEL::FG_SERIAL_GARMIN_IN ) {
433         read_garmin_in(p);
434     } else if ( p.kind == fgIOCHANNEL::FG_SERIAL_FGFS_OUT ) {
435         send_fgfs_out(p);
436     } else if ( p.kind == fgIOCHANNEL::FG_SERIAL_FGFS_IN ) {
437         read_fgfs_in(p);
438     }
439 }
440
441
442 // process any serial port work
443 void fgSerialProcess() {
444     fgIOCHANNEL port;
445     
446     const_io_iterator current = port_list.begin();
447     const_io_iterator last = port_list.end();
448
449     for ( ; current != last; ++current ) {
450         port = *current;
451         if ( port.kind != fgIOCHANNEL::FG_SERIAL_DISABLED ) {
452             process_port ( port );
453         }
454     }
455 }
456
457
458 // $Log$
459 // Revision 1.8  1999/01/19 20:57:04  curt
460 // MacOS portability changes contributed by "Robert Puyol" <puyol@abvent.fr>
461 //
462 // Revision 1.7  1998/12/05 15:54:21  curt
463 // Renamed class fgFLIGHT to class FGState as per request by JSB.
464 //
465 // Revision 1.6  1998/12/03 01:17:18  curt
466 // Converted fgFLIGHT to a class.
467 //
468 // Revision 1.5  1998/11/30 17:43:32  curt
469 // Lots of tweaking to get serial output to actually work.
470 //
471 // Revision 1.4  1998/11/25 01:33:58  curt
472 // Support for an arbitrary number of serial ports.
473 //
474 // Revision 1.3  1998/11/23 20:51:51  curt
475 // Tweaking serial stuff.
476 //
477 // Revision 1.2  1998/11/19 13:53:25  curt
478 // Added a "Garmin" mode.
479 //
480 // Revision 1.1  1998/11/16 13:57:42  curt
481 // Initial revision.
482 //