25#include <simgear/compiler.h>
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>
67#include <simgear/io/SGDataDistributionService.hxx>
84FGIO::parse_port_config(
const string& config,
bool& o_ok )
86 SG_LOG( SG_IO, SG_INFO,
"Parse I/O channel request: " << config );
87 string_list tokens = simgear::strutils::split( config,
"," );
90 SG_LOG( SG_IO, SG_ALERT,
91 "Port configuration error: empty config string" );
96 return parse_port_config(tokens, o_ok);
100FGIO::parse_port_config(
const string_list& tokens,
bool& o_ok )
103 const string protocol = tokens[0];
104 SG_LOG( SG_IO, SG_INFO,
" protocol = " << protocol );
106 FGProtocol *io =
nullptr;
109 if ( protocol ==
"atcsim" ) {
110 FGATCMain *atcsim =
new FGATCMain;
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" );
119 if ( tokens[1] ==
"no-pedals" ) {
120 fgSetBool(
"/input/atcsim/ignore-pedal-controls",
true );
122 fgSetBool(
"/input/atcsim/ignore-pedal-controls",
false );
124 atcsim->
set_path_names(tokens[2], tokens[3], tokens[4], tokens[5]);
127 }
else if ( protocol ==
"atlas" ) {
129 }
else if ( protocol ==
"opengc" ) {
131 }
else if ( protocol ==
"AV400" ) {
133 }
else if ( protocol ==
"AV400Sim" ) {
135 }
else if ( protocol ==
"AV400WSimA" ) {
136 io =
new FGAV400WSimA;
137 }
else if ( protocol ==
"AV400WSimB" ) {
138 io =
new FGAV400WSimB;
139 }
else if ( protocol ==
"flarm" ) {
141 }
else if ( protocol ==
"garmin" ) {
143 }
else if ( protocol ==
"igc" ) {
144 io =
new IGCProtocol;
145 }
else if ( protocol ==
"joyclient" ) {
146 io =
new FGJoyClient;
147 }
else if ( protocol ==
"jsclient" ) {
149 }
else if ( protocol ==
"native" ) {
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" ) {
160 }
else if ( protocol ==
"dds-props") {
163 }
else if ( protocol ==
"props" || protocol ==
"telnet" ) {
164 io =
new FGProps( tokens );
167 }
else if ( protocol ==
"pve" ) {
169 }
else if ( protocol ==
"ray" ) {
171 }
else if ( protocol ==
"rul" ) {
173 }
else if ( protocol ==
"generic" ) {
174 FGGeneric *
generic =
new FGGeneric( tokens );
175 if (!generic->getInitOk())
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)" );
188 string dir = tokens[1];
189 int rate =
atoi(tokens[2].c_str());
190 string host = tokens[3];
192 short port =
atoi(tokens[4].c_str());
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);
214 else if ( protocol ==
"hla" ) {
216 return new FGHLA(tokens);
218 else if ( protocol ==
"hla-local" ) {
220 if (tokens.size() != 2) {
221 SG_LOG( SG_IO, SG_ALERT,
"Ignoring invalid --hla-local option "
222 "(one argument expected: --hla-local=<federationname>" );
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");
231 return new FGHLA(HLA_tokens);
238 catch (FGProtocolConfigError& err)
240 SG_LOG( SG_IO, SG_ALERT,
"Port configuration error: " << err.what() );
245 if (tokens.size() < 4) {
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");
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");
258 string medium = tokens[1];
259 SG_LOG( SG_IO, SG_INFO,
" medium = " << medium );
261 string direction = tokens[2];
263 SG_LOG( SG_IO, SG_INFO,
" direction = " << direction );
265 string hertz_str = tokens[3];
266 double hertz =
atof( hertz_str.c_str() );
268 SG_LOG( SG_IO, SG_INFO,
" hertz = " << hertz );
271 const auto name = generateName(protocol);
273 SG_LOG(SG_IO, SG_INFO,
" name = " <<
name);
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");
283 string device = tokens[4];
284 SG_LOG( SG_IO, SG_INFO,
" device = " << device );
287 string baud = tokens[5];
288 SG_LOG( SG_IO, SG_INFO,
" baud = " << baud );
291 SGSerial *ch =
new SGSerial( device, baud );
294 if ( protocol ==
"AV400WSimB" ) {
295 if ( tokens.size() < 7 ) {
296 SG_LOG( SG_IO, SG_ALERT,
"Missing second hz for AV400WSimB.");
300 FGAV400WSimB *fgavb =
static_cast<FGAV400WSimB*
>(io);
301 string hz2_str = tokens[6];
302 double hz2 =
atof(hz2_str.c_str());
305 }
else if ( medium ==
"file" ) {
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)");
314 string file = tokens[4];
315 SG_LOG( SG_IO, SG_INFO,
" file name = " << file );
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);
322 generic->setExitOnError(
true);
327 SGFile *ch =
new SGFile( file, repeat );
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)");
337 string port = tokens[5];
338 string style = tokens[6];
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 );
345 SG_LOG(SG_IO, SG_ALERT,
"Non-Positive Hz rate may block generic I/O ");
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.");
358 else if ( medium ==
"dds") {
364 SG_LOG( SG_IO, SG_ALERT,
"Unknown transport medium \"" << medium <<
"\" for \"" << protocol <<
"\"");
381 _realDeltaTime =
fgGetNode(
"/sim/time/delta-realtime-sec");
387 for (
const auto& config : *(
globals->get_channel_options_list())) {
390 SG_LOG( SG_IO, SG_DEBUG,
"add_channel() with config=" << config <<
" => ok=" << ok <<
" p=" <<
p);
393 addToPropertyTree(
p->get_name(), config);
397 SG_LOG( SG_IO, SG_ALERT,
"add_channel() failed. config=" << config);
401 auto cmdMgr =
globals->get_commands();
402 cmdMgr->addCommand(
"add-io-channel",
this, &FGIO::commandAddChannel);
403 cmdMgr->addCommand(
"remove-io-channel",
this, &FGIO::commandRemoveChannel);
407FGProtocol* FGIO::add_channel(
const string& config,
bool& o_ok)
414 SG_LOG(SG_IO, SG_ALERT,
"Failed to parse config=" << config);
422 if ( !
p->is_enabled() ) {
423 SG_LOG( SG_IO, SG_ALERT,
"I/O Channel config failed." );
428 io_channels.push_back(
p );
435 SG_LOG(SG_IO, SG_INFO,
"FGIO::reinit()");
437 std::for_each(io_channels.begin(), io_channels.end(), [](
FGProtocol*
p) {
438 SG_LOG(SG_IO, SG_INFO,
"Restarting channel \"" << p->get_name() <<
"\"");
450 double delta_time_sec = _realDeltaTime->getDoubleValue();
452 ProtocolVec::iterator
i = io_channels.begin();
453 ProtocolVec::iterator end = io_channels.end();
454 for (;
i != end; ++
i ) {
456 if (!
p->is_enabled()) {
460 p->dec_count_down( delta_time_sec );
461 double dt = 1 /
p->get_hz();
462 if (
p->get_count_down() < 0.33 * dt ) {
465 while (
p->get_count_down() < 0.33 * dt ) {
466 p->inc_count_down( dt );
475 ProtocolVec::iterator
i = io_channels.begin();
476 ProtocolVec::iterator end = io_channels.end();
477 for (;
i != end; ++
i )
480 if (
p->is_enabled() ) {
483 SG_LOG(SG_IO, SG_INFO,
"Shutting down channel \"" <<
p->get_name() <<
"\"");
490 auto cmdMgr =
globals->get_commands();
491 cmdMgr->removeCommand(
"add-io-channel");
492 cmdMgr->removeCommand(
"remove-io-channel");
508 std::string txAddress =
fgGetString(
"/sim/multiplay/txhost");
509 if (!txAddress.empty())
return true;
514 auto channels =
globals->get_channel_options_list();
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();
524bool FGIO::commandAddChannel(
const SGPropertyNode * arg, SGPropertyNode * root)
526 if (!arg->hasChild(
"config")) {
527 SG_LOG(SG_NETWORK, SG_WARN,
"add-io-channel: missing 'config' argument");
531 string name = arg->getStringValue(
"name");
532 const string config = arg->getStringValue(
"config");
534 auto protocol = add_channel(config, ok);
536 SG_LOG(SG_NETWORK, SG_WARN,
"add-io-channel: adding channel failed");
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);
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;
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());
557 SG_LOG(SG_IO, SG_INFO,
"add-io-channel: setting name to \"" << validName <<
"\"");
558 protocol->set_name(validName);
563 addToPropertyTree(protocol->get_name(), config);
568bool FGIO::commandRemoveChannel(
const SGPropertyNode * arg, SGPropertyNode * root)
570 if (!arg->hasChild(
"name")) {
571 SG_LOG(SG_NETWORK, SG_WARN,
"remove-io-channel: missing 'name' argument");
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);
583 removeFromPropertyTree(
name);
586 if (
p->is_enabled()) {
590 io_channels.erase(it);
595FGIO::generateName(
const string protocol)
599 for (
int i = 1;
i < 1000;
i++) {
600 name = protocol +
"-" + to_string(
i);
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()) {
611void FGIO::addToPropertyTree(
const string name,
const string config)
614 channelNode->setStringValue(
"config", config);
615 channelNode->setStringValue(
"name",
name);
618void FGIO::removeFromPropertyTree(
const string name)
621 if (node->hasChild(
name)) {
622 node->removeChild(
name);
void set_path_names(const SGPath &in0, const SGPath &in1, const SGPath &out0, const SGPath &out1)
void update(double dt) override
static bool isMultiplayerRequested()
helper to determine early in startup, if MP will be used.
void set_io_channel(SGIOChannel *c)
void set_name(const std::string &n)
void set_direction(const std::string &d)
SGSubsystemMgr::Registrant< FGIO > registrantFGIO
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
bool fgSetInt(const char *name, int val)
Set an int value for a property.
std::vector< std::string > string_list
static double atof(const string &str)
static int atoi(const string &str)
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.