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