1 // fg_io.cxx -- higher level I/O channel management routines
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 #include "config.h"
24
25 #include <simgear/compiler.h>
26
27 #include <cstdlib> // atoi()
28
29 #include <string>
30 #include <algorithm>
31
32 #include <simgear/debug/logstream.hxx>
33 #include <simgear/io/iochannel.hxx>
34 #include <simgear/io/sg_file.hxx>
35 #include <simgear/io/sg_serial.hxx>
36 #include <simgear/io/sg_socket.hxx>
37 #include <simgear/io/sg_socket_udp.hxx>
38 #include <simgear/math/sg_types.hxx>
39 #include <simgear/timing/timestamp.hxx>
40 #include <simgear/misc/strutils.hxx>
41 #include <simgear/structure/commands.hxx>
42
43 #include <Network/protocol.hxx>
44 #include <Network/ATC-Main.hxx>
45 #include <Network/atlas.hxx>
46 #include <Network/AV400.hxx>
47 #include <Network/AV400Sim.hxx>
48 #include <Network/AV400WSim.hxx>
49 #include <Network/flarm.hxx>
50 #include <Network/garmin.hxx>
51 #include <Network/igc.hxx>
52 #include <Network/joyclient.hxx>
53 #include <Network/jsclient.hxx>
54 #include <Network/native.hxx>
55 #include <Network/native_ctrls.hxx>
56 #include <Network/native_fdm.hxx>
57 #include <Network/native_gui.hxx>
58 #include <Network/opengc.hxx>
59 #include <Network/nmea.hxx>
60 #include <Network/props.hxx>
61 #include <Network/pve.hxx>
62 #include <Network/ray.hxx>
63 #include <Network/rul.hxx>
64 #include <Network/generic.hxx>
65
66 #if FG_HAVE_HLA
67 #include <Network/HLA/hla.hxx>
68 #endif
69
70 #include "globals.hxx"
71 #include "fg_io.hxx"
72
73 using std::atoi;
74 using std::string;
75 using std::to_string;
76
77 // configure a port based on the config string
78
79 FGProtocol*
parse_port_config(const string & config)80 FGIO::parse_port_config( const string& config )
81 {
82 SG_LOG( SG_IO, SG_INFO, "Parse I/O channel request: " << config );
83 string_list tokens = simgear::strutils::split( config, "," );
84 if (tokens.empty())
85 {
86 SG_LOG( SG_IO, SG_ALERT,
87 "Port configuration error: empty config string" );
88 return nullptr;
89 }
90
91 return parse_port_config(tokens);
92 }
93
94 FGProtocol*
parse_port_config(const string_list & tokens)95 FGIO::parse_port_config( const string_list& tokens )
96 {
97 const string protocol = tokens[0];
98 SG_LOG( SG_IO, SG_INFO, " protocol = " << protocol );
99
100 FGProtocol *io = nullptr;
101 try
102 {
103 if ( protocol == "atcsim" ) {
104 FGATCMain *atcsim = new FGATCMain;
105 atcsim->set_hz( 30 );
106 if ( tokens.size() != 6 ) {
107 SG_LOG( SG_IO, SG_ALERT, "Usage: --atcsim=[no-]pedals,"
108 << "input0_config,input1_config,"
109 << "output0_config,output1_config,file.nas" );
110 delete atcsim;
111 return NULL;
112 }
113 if ( tokens[1] == "no-pedals" ) {
114 fgSetBool( "/input/atcsim/ignore-pedal-controls", true );
115 } else {
116 fgSetBool( "/input/atcsim/ignore-pedal-controls", false );
117 }
118 atcsim->set_path_names(tokens[2], tokens[3], tokens[4], tokens[5]);
119 return atcsim;
120 } else if ( protocol == "atlas" ) {
121 io = new FGAtlas;
122 } else if ( protocol == "opengc" ) {
123 io = new FGOpenGC;
124 } else if ( protocol == "AV400" ) {
125 io = new FGAV400;
126 } else if ( protocol == "AV400Sim" ) {
127 io = new FGAV400Sim;
128 } else if ( protocol == "AV400WSimA" ) {
129 io = new FGAV400WSimA;
130 } else if ( protocol == "AV400WSimB" ) {
131 io = new FGAV400WSimB;
132 } else if ( protocol == "flarm" ) {
133 io = new FGFlarm();
134 } else if ( protocol == "garmin" ) {
135 io = new FGGarmin();
136 } else if ( protocol == "igc" ) {
137 io = new IGCProtocol;
138 } else if ( protocol == "joyclient" ) {
139 io = new FGJoyClient;
140 } else if ( protocol == "jsclient" ) {
141 io = new FGJsClient;
142 } else if ( protocol == "native" ) {
143 io = new FGNative;
144 } else if ( protocol == "native-ctrls" ) {
145 io = new FGNativeCtrls;
146 } else if ( protocol == "native-fdm" ) {
147 io = new FGNativeFDM;
148 } else if ( protocol == "native-gui" ) {
149 io = new FGNativeGUI;
150 } else if ( protocol == "nmea" ) {
151 io = new FGNMEA();
152 } else if ( protocol == "props" || protocol == "telnet" ) {
153 io = new FGProps( tokens );
154 return io;
155 } else if ( protocol == "pve" ) {
156 io = new FGPVE;
157 } else if ( protocol == "ray" ) {
158 io = new FGRAY;
159 } else if ( protocol == "rul" ) {
160 io = new FGRUL;
161 } else if ( protocol == "generic" ) {
162 FGGeneric *generic = new FGGeneric( tokens );
163 if (!generic->getInitOk())
164 {
165 // failed to initialize (i.e. invalid configuration)
166 delete generic;
167 return NULL;
168 }
169 io = generic;
170 } else if ( protocol == "multiplay" ) {
171 if ( tokens.size() != 5 ) {
172 SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --multiplay option "
173 "(4 arguments expected: --multiplay=dir,hz,hostname,port)" );
174 return NULL;
175 }
176 string dir = tokens[1];
177 int rate = atoi(tokens[2].c_str());
178 string host = tokens[3];
179
180 short port = atoi(tokens[4].c_str());
181
182 // multiplay used to be handled by an FGProtocol, but no longer. This code
183 // retains compatibility with existing command-line syntax
184 if (dir == "in") {
185 fgSetInt("/sim/multiplay/rxport", port);
186 fgSetString("/sim/multiplay/rxhost", host.c_str());
187 } else if (dir == "out") {
188 fgSetInt("/sim/multiplay/txport", port);
189 fgSetString("/sim/multiplay/txhost", host.c_str());
190 fgSetInt("/sim/multiplay/tx-rate-hz", rate);
191 }
192
193 return NULL;
194 }
195 #if FG_HAVE_HLA
196 else if ( protocol == "hla" ) {
197 return new FGHLA(tokens);
198 }
199 else if ( protocol == "hla-local" ) {
200 // This is just about to bring up some defaults
201 if (tokens.size() != 2) {
202 SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --hla-local option "
203 "(one argument expected: --hla-local=<federationname>" );
204 return NULL;
205 }
206 std::vector<std::string> HLA_tokens (tokens);
207 HLA_tokens.insert(HLA_tokens.begin(), "");
208 HLA_tokens.insert(HLA_tokens.begin(), "60");
209 HLA_tokens.insert(HLA_tokens.begin(), "bi");
210 HLA_tokens.push_back("fg-local.xml");
211 return new FGHLA(HLA_tokens);
212 }
213 #endif
214 else {
215 return NULL;
216 }
217 }
218 catch (FGProtocolConfigError& err)
219 {
220 SG_LOG( SG_IO, SG_ALERT, "Port configuration error: " << err.what() );
221 delete io;
222 return NULL;
223 }
224
225 if (tokens.size() < 4) {
226 SG_LOG( SG_IO, SG_ALERT, "Too few arguments for network protocol. At least 3 arguments required. " <<
227 "Usage: --" << protocol << "=(file|socket|serial), (in|out|bi), hertz");
228 delete io;
229 return NULL;
230 }
231 string medium = tokens[1];
232 SG_LOG( SG_IO, SG_INFO, " medium = " << medium );
233
234 string direction = tokens[2];
235 io->set_direction( direction );
236 SG_LOG( SG_IO, SG_INFO, " direction = " << direction );
237
238 string hertz_str = tokens[3];
239 double hertz = atof( hertz_str.c_str() );
240 io->set_hz( hertz );
241 SG_LOG( SG_IO, SG_INFO, " hertz = " << hertz );
242
243 // name
244 const auto name = generateName(protocol);
245 io->set_name(name);
246 SG_LOG(SG_IO, SG_INFO, " name = " << name);
247
248 if ( medium == "serial" ) {
249 if ( tokens.size() < 6) {
250 SG_LOG( SG_IO, SG_ALERT, "Too few arguments for serial communications. " <<
251 "Usage --" << protocol << "=serial, (in|out|bi), hertz, device, baudrate");
252 delete io;
253 return NULL;
254 }
255 // device name
256 string device = tokens[4];
257 SG_LOG( SG_IO, SG_INFO, " device = " << device );
258
259 // baud
260 string baud = tokens[5];
261 SG_LOG( SG_IO, SG_INFO, " baud = " << baud );
262
263
264 SGSerial *ch = new SGSerial( device, baud );
265 io->set_io_channel( ch );
266
267 if ( protocol == "AV400WSimB" ) {
268 if ( tokens.size() < 7 ) {
269 SG_LOG( SG_IO, SG_ALERT, "Missing second hz for AV400WSimB.");
270 delete io;
271 return NULL;
272 }
273 FGAV400WSimB *fgavb = static_cast<FGAV400WSimB*>(io);
274 string hz2_str = tokens[6];
275 double hz2 = atof(hz2_str.c_str());
276 fgavb->set_hz2(hz2);
277 }
278 } else if ( medium == "file" ) {
279 // file name
280 if ( tokens.size() < 5) {
281 SG_LOG( SG_IO, SG_ALERT, "Too few arguments for file I/O. " <<
282 "Usage --" << protocol << "=file, (in|out), hertz, filename (,repeat)");
283 delete io;
284 return NULL;
285 }
286
287 string file = tokens[4];
288 SG_LOG( SG_IO, SG_INFO, " file name = " << file );
289 int repeat = 1;
290 if (tokens.size() >= 7 && tokens[6] == "repeat") {
291 if (tokens.size() >= 8) {
292 repeat = atoi(tokens[7].c_str());
293 FGGeneric* generic = dynamic_cast<FGGeneric*>(io);
294 if (generic)
295 generic->setExitOnError(true);
296 } else {
297 repeat = -1;
298 }
299 }
300 SGFile *ch = new SGFile( file, repeat );
301 io->set_io_channel( ch );
302 } else if ( medium == "socket" ) {
303 if ( tokens.size() < 7) {
304 SG_LOG( SG_IO, SG_ALERT, "Too few arguments for socket communications. " <<
305 "Usage --" << protocol << "=socket, (in|out|bi), hertz, hostname, port, (tcp|udp)");
306 delete io;
307 return NULL;
308 }
309 string hostname = tokens[4];
310 string port = tokens[5];
311 string style = tokens[6];
312
313 SG_LOG( SG_IO, SG_INFO, " hostname = " << hostname );
314 SG_LOG( SG_IO, SG_INFO, " port = " << port );
315 SG_LOG( SG_IO, SG_INFO, " style = " << style );
316
317 if (hertz <= 0) {
318 SG_LOG(SG_IO, SG_ALERT, "Non-Positive Hz rate may block generic I/O ");
319 }
320
321 io->set_io_channel( new SGSocket( hostname, port, style ) );
322 }
323 else
324 {
325 SG_LOG( SG_IO, SG_ALERT, "Unknown transport medium \"" << medium << "\" for \"" << protocol << "\"");
326 delete io;
327 return nullptr;
328 }
329
330 return io;
331 }
332
333
334 // step through the port config streams (from fgOPTIONS) and setup
335 // serial port channels for each
336 void
init()337 FGIO::init()
338 {
339 // SG_LOG( SG_IO, SG_INFO, "I/O Channel initialization, " <<
340 // globals->get_channel_options_list()->size() << " requests." );
341
342 _realDeltaTime = fgGetNode("/sim/time/delta-realtime-sec");
343
344 // we could almost do this in a single step except pushing a valid
345 // port onto the port list copies the structure and destroys the
346 // original, which closes the port and frees up the fd ... doh!!!
347
348 for (const auto& config : *(globals->get_channel_options_list())) {
349 FGProtocol* p = add_channel(config);
350 if (p) {
351 addToPropertyTree(p->get_name(), config);
352 }
353 } // of channel options iteration
354
355 auto cmdMgr = globals->get_commands();
356 cmdMgr->addCommand("add-io-channel", this, &FGIO::commandAddChannel);
357 cmdMgr->addCommand("remove-io-channel", this, &FGIO::commandRemoveChannel);
358 }
359
360 // add another I/O channel
add_channel(const string & config)361 FGProtocol* FGIO::add_channel(const string& config)
362 {
363 // parse the configuration string and store the results in the
364 // appropriate FGIOChannel structure
365 FGProtocol *p = parse_port_config( config );
366 if (!p)
367 {
368 return nullptr;
369 }
370
371 p->open();
372 if ( !p->is_enabled() ) {
373 SG_LOG( SG_IO, SG_ALERT, "I/O Channel config failed." );
374 delete p;
375 return nullptr;
376 }
377
378 io_channels.push_back( p );
379 return p;
380 }
381
382 void
reinit()383 FGIO::reinit()
384 {
385 SG_LOG(SG_IO, SG_INFO, "FGIO::reinit()");
386
387 std::for_each(io_channels.begin(), io_channels.end(), [](FGProtocol* p) {
388 SG_LOG(SG_IO, SG_INFO, "Restarting channel \"" << p->get_name() << "\"");
389 p->reinit();
390 });
391 }
392
393 // process any IO channel work
394 void
update(double)395 FGIO::update( double /* delta_time_sec */ )
396 {
397 // use wall-clock, not simulation, delta time, so that network
398 // protocols update when the simulation is paused
399 // see http://code.google.com/p/flightgear-bugs/issues/detail?id=125
400 double delta_time_sec = _realDeltaTime->getDoubleValue();
401
402 ProtocolVec::iterator i = io_channels.begin();
403 ProtocolVec::iterator end = io_channels.end();
404 for (; i != end; ++i ) {
405 FGProtocol* p = *i;
406 if (!p->is_enabled()) {
407 continue;
408 }
409
410 p->dec_count_down( delta_time_sec );
411 double dt = 1 / p->get_hz();
412 if ( p->get_count_down() < 0.33 * dt ) {
413 p->process();
414 p->inc_count();
415 while ( p->get_count_down() < 0.33 * dt ) {
416 p->inc_count_down( dt );
417 }
418 } // of channel processing
419 } // of io_channels iteration
420 }
421
422 void
shutdown()423 FGIO::shutdown()
424 {
425 ProtocolVec::iterator i = io_channels.begin();
426 ProtocolVec::iterator end = io_channels.end();
427 for (; i != end; ++i )
428 {
429 FGProtocol *p = *i;
430 if ( p->is_enabled() ) {
431 p->close();
432 }
433 SG_LOG(SG_IO, SG_INFO, "Shutting down channel \"" << p->get_name() << "\"");
434
435 delete p;
436 }
437
438 io_channels.clear();
439
440 auto cmdMgr = globals->get_commands();
441 cmdMgr->removeCommand("add-io-channel");
442 cmdMgr->removeCommand("remove-io-channel");
443 }
444
445 void
bind()446 FGIO::bind()
447 {
448 }
449
450 void
unbind()451 FGIO::unbind()
452 {
453 }
454
isMultiplayerRequested()455 bool FGIO::isMultiplayerRequested()
456 {
457 // launcher sets these properties directly, as does the in-sim dialog
458 std::string txAddress = fgGetString("/sim/multiplay/txhost");
459 if (!txAddress.empty()) return true;
460
461 // check the channel options list for a multiplay setting - this
462 // is easier than checking the raw Options arguments, but works before
463 // this subsytem is actually created.
464 auto channels = globals->get_channel_options_list();
465 if (!channels)
466 return false; // happens running tests
467
468 auto it = std::find_if(channels->begin(), channels->end(),
469 [](const std::string& channelOption)
470 { return (channelOption.find("multiplay") == 0); });
471 return it != channels->end();
472 }
473
commandAddChannel(const SGPropertyNode * arg,SGPropertyNode * root)474 bool FGIO::commandAddChannel(const SGPropertyNode * arg, SGPropertyNode * root)
475 {
476 if (!arg->hasChild("config")) {
477 SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: missing 'config' argument");
478 return false;
479 }
480
481 string name = arg->getStringValue("name");
482 const string config = arg->getStringValue("config");
483 auto protocol = add_channel(config);
484 if (!protocol) {
485 SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: adding channel failed");
486 return false;
487 }
488
489 if (!name.empty()) {
490 const string validName = simgear::strutils::makeStringSafeForPropertyName(name);
491 if (name.compare(validName) != 0) {
492 SG_LOG(SG_IO, SG_WARN, "add-io-channel: replaced illegal characters: " << name << " -> " << validName);
493 }
494 if (!validName.empty()) {
495 auto it = std::find_if(io_channels.begin(), io_channels.end(), [&validName](const FGProtocol* proto) {
496 return proto->get_name() == validName;
497 });
498
499 if (it != io_channels.end()) {
500 SG_LOG(SG_IO, SG_WARN, "add-io-channel: channel name \"" << validName << "\" already exists, using " << protocol->get_name());
501 } else {
502 // set custom name instead of auto-generated name:
503 SG_LOG(SG_IO, SG_INFO, "add-io-channel: setting name to \"" << validName << "\"");
504 protocol->set_name(validName);
505 }
506 }
507 }
508 // add entry to /io/channels/<name>
509 addToPropertyTree(protocol->get_name(), config);
510
511 return true;
512 }
513
commandRemoveChannel(const SGPropertyNode * arg,SGPropertyNode * root)514 bool FGIO::commandRemoveChannel(const SGPropertyNode * arg, SGPropertyNode * root)
515 {
516 if (!arg->hasChild("name")) {
517 SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: missing 'name' argument");
518 }
519
520 const string name = arg->getStringValue("name");
521 auto it = find_if(io_channels.begin(), io_channels.end(),
522 [name](const FGProtocol* proto)
523 { return proto->get_name() == name; });
524 if (it == io_channels.end()) {
525 SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: no channel with name:" + name);
526 return false;
527 }
528
529 removeFromPropertyTree(name);
530
531 FGProtocol* p = *it;
532 if (p->is_enabled()) {
533 p->close();
534 }
535 delete p;
536 io_channels.erase(it);
537 return true;
538 }
539
540 string
generateName(const string protocol)541 FGIO::generateName(const string protocol)
542 {
543 string name;
544 // Find first unused name:
545 for (int i = 1; i < 1000; i++) {
546 name = protocol + "-" + to_string(i);
547
548 auto it = find_if(io_channels.begin(), io_channels.end(),
549 [name](const FGProtocol* proto) { return proto->get_name() == name; });
550 if (it == io_channels.end()) {
551 break;
552 }
553 }
554 return name;
555 }
556
addToPropertyTree(const string name,const string config)557 void FGIO::addToPropertyTree(const string name, const string config)
558 {
559 auto channelNode = fgGetNode("/io/channels/" + name, true);
560 channelNode->setStringValue("config", config);
561 channelNode->setStringValue("name", name);
562 }
563
removeFromPropertyTree(const string name)564 void FGIO::removeFromPropertyTree(const string name)
565 {
566 auto node = fgGetNode("/io/channels");
567 if (node->hasChild(name)) {
568 node->removeChild(name);
569 }
570 }
571
572
573 // Register the subsystem.
574 SGSubsystemMgr::Registrant<FGIO> registrantFGIO;
575