2 // Property server class.
4 // Written by Curtis Olson, started September 2000.
5 // Modified by Bernie Bright, May 2002.
6 // Modified by Jean-Paul Anceaux, Dec 2015.
8 // Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License as
12 // published by the Free Software Foundation; either version 2 of the
13 // License, or (at your option) any later version.
15 // This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 // General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 #include <simgear/compiler.h>
32 #include <simgear/debug/logstream.hxx>
33 #include <simgear/structure/commands.hxx>
34 #include <simgear/misc/strutils.hxx>
35 #include <simgear/props/props.hxx>
36 #include <simgear/props/props_io.hxx>
37 #include <simgear/structure/exception.hxx>
43 #include <Main/globals.hxx>
44 #include <Viewer/viewmgr.hxx>
46 #include <simgear/io/sg_netChat.hxx>
48 #include <simgear/misc/strutils.hxx>
56 #include <boost/foreach.hpp>
58 using std::stringstream;
65 * Props connection class.
66 * This class represents a connection to props client.
68 class PropsChannel : public simgear::NetChat, public SGPropertyChangeListener
70 simgear::NetBuffer buffer;
73 * Current property node name.
91 * Append incoming data to our request buffer.
93 * @param s Character string to append to buffer
94 * @param n Number of characters to append.
96 void collectIncomingData( const char* s, int n );
99 * Process a complete request from the props client.
101 void foundTerminator();
103 // callback for registered listeners (subscriptions)
104 void valueChanged(SGPropertyNode *node);
107 typedef string_list ParameterList;
109 inline void node_not_found_error( const string& s ) const {
110 throw "node '" + s + "' not found";
113 void error(std::string msg) { // wrapper: prints errors to STDERR and to the telnet client
114 push( msg.c_str() ); push( getTerminator() );
115 SG_LOG(SG_NETWORK, SG_ALERT, __FILE__<<"@" << __LINE__ <<" in " << __FUNCTION__ <<":"<< msg.c_str() << std::endl);
119 bool check_args(const ParameterList &tok, const unsigned int num, const char* func) {
120 if (tok.size()-1 < num) {
121 error(string("Error:Wrong argument count for:")+string(func) );
127 std::vector<SGPropertyNode_ptr> _listeners;
128 typedef void (PropsChannel::*TelnetCallback) (const ParameterList&);
129 std::map<std::string, TelnetCallback> callback_map;
131 // callback implementations:
132 void subscribe(const ParameterList &p);
133 void unsubscribe(const ParameterList &p);
139 PropsChannel::PropsChannel()
144 setTerminator( "\r\n" );
145 callback_map["subscribe"] = &PropsChannel::subscribe;
146 callback_map["unsubscribe"] = &PropsChannel::unsubscribe;
149 PropsChannel::~PropsChannel() {
150 // clean up all registered listeners
151 BOOST_FOREACH(SGPropertyNode_ptr l, _listeners) {
152 l->removeChangeListener( this );
156 void PropsChannel::subscribe(const ParameterList ¶m) {
157 if (! check_args(param,1,"subscribe")) return;
159 std::string command = param[0];
160 const char* p = param[1].c_str();
163 //SG_LOG(SG_GENERAL, SG_ALERT, p << std::endl);
164 push( command.c_str() ); push ( " " );
166 push( getTerminator() );
168 SGPropertyNode *n = globals->get_props()->getNode( p,true );
170 error("Error:Tied properties cannot register listeners");
175 n->addChangeListener( this );
176 _listeners.push_back( n ); // housekeeping, save for deletion in dtor later on
178 error("listener could not be added");
182 void PropsChannel::unsubscribe(const ParameterList ¶m) {
183 if (!check_args(param,1,"unsubscribe")) return;
186 SGPropertyNode *n = globals->get_props()->getNode( param[1].c_str() );
188 n->removeChangeListener( this );
189 } catch (sg_exception&) {
190 error("Error:Listener could not be removed");
195 //TODO: provide support for different types of subscriptions MODES ? (child added/removed, thesholds, min/max)
196 void PropsChannel::valueChanged(SGPropertyNode* ptr) {
197 //SG_LOG(SG_GENERAL, SG_ALERT, __FILE__<< "@"<<__LINE__ << ":" << __FUNCTION__ << std::endl);
198 std::stringstream response;
199 response << ptr->getPath(true) << "=" << ptr->getStringValue() << getTerminator(); //TODO: use hashes, echo several properties at once
200 push( response.str().c_str() );
207 PropsChannel::collectIncomingData( const char* s, int n )
209 buffer.append( s, n );
212 // return a human readable form of the value "type"
214 getValueTypeString( const SGPropertyNode *node )
216 using namespace simgear;
225 props::Type type = node->getType();
226 if ( type == props::UNSPECIFIED ) {
227 result = "unspecified";
228 } else if ( type == props::NONE ) {
230 } else if ( type == props::BOOL ) {
232 } else if ( type == props::INT ) {
234 } else if ( type == props::LONG ) {
236 } else if ( type == props::FLOAT ) {
238 } else if ( type == props::DOUBLE ) {
240 } else if ( type == props::STRING ) {
252 PropsChannel::foundTerminator()
254 const char* cmd = buffer.getData();
255 SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
257 ParameterList tokens = simgear::strutils::split( cmd );
259 SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
262 if (!tokens.empty()) {
263 string command = tokens[0];
265 if (command == "ls") {
266 SGPropertyNode* dir = node;
267 if (tokens.size() == 2) {
268 if (tokens[1][0] == '/') {
269 dir = globals->get_props()->getNode( tokens[1].c_str() );
274 dir = globals->get_props()->getNode( s.c_str() );
278 node_not_found_error( tokens[1] );
282 for (int i = 0; i < dir->nChildren(); i++) {
283 SGPropertyNode * child = dir->getChild(i);
284 string line = child->getDisplayName(true);
286 if ( child->nChildren() > 0 ) {
289 if (mode == PROMPT) {
290 string value = child->getStringValue();
291 line += " =\t'" + value + "'\t(";
292 line += getValueTypeString( child );
297 line += getTerminator();
298 push( line.c_str() );
300 } else if ( command == "dump" ) {
302 if ( tokens.size() <= 1 ) {
303 writeProperties( buf, node );
304 buf << ends; // null terminate the string
305 push( buf.str().c_str() );
306 push( getTerminator() );
308 SGPropertyNode *child = node->getNode( tokens[1].c_str() );
310 writeProperties ( buf, child );
311 buf << ends; // null terminate the string
312 push( buf.str().c_str() );
313 push( getTerminator() );
315 node_not_found_error( tokens[1] );
319 else if ( command == "cd" ) {
320 if (tokens.size() == 2) {
321 SGPropertyNode* child = node->getNode( tokens[1].c_str() );
324 path = node->getPath();
326 node_not_found_error( tokens[1] );
329 } else if ( command == "pwd" ) {
330 string pwd = node->getPath();
336 push( getTerminator() );
337 } else if ( command == "get" || command == "show" ) {
338 if ( tokens.size() == 2 ) {
340 string value = node->getStringValue ( tokens[1].c_str(), "" );
341 if ( mode == PROMPT ) {
346 tmp += getValueTypeString(
347 node->getNode( tokens[1].c_str() ) );
353 push( getTerminator() );
355 } else if ( command == "set" ) {
356 if ( tokens.size() >= 2 ) {
358 for (unsigned int i = 2; i < tokens.size(); i++) {
363 node->getNode( tokens[1].c_str(), true )
364 ->setStringValue(value.c_str());
366 if ( mode == PROMPT ) {
367 // now fetch and write out the new value as confirmation
369 value = node->getStringValue ( tokens[1].c_str(), "" );
370 tmp = tokens[1] + " = '" + value + "' (";
371 tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
374 push( getTerminator() );
377 } else if ( command == "reinit" ) {
378 if ( tokens.size() == 2 ) {
381 for ( unsigned int i = 1; i < tokens.size(); ++i ) {
382 cout << "props: adding subsystem = " << tokens[i] << endl;
383 SGPropertyNode *node = args.getNode("subsystem", i-1, true);
384 node->setStringValue( tokens[i].c_str() );
386 if ( !globals->get_commands()
387 ->execute( "reinit", &args) )
389 SG_LOG( SG_NETWORK, SG_ALERT,
390 "Command " << tokens[1] << " failed.");
391 if ( mode == PROMPT ) {
394 push( getTerminator() );
397 if ( mode == PROMPT ) {
398 tmp += "<completed>";
400 push( getTerminator() );
404 } else if ( command == "run" ) {
406 if ( tokens.size() >= 2 ) {
408 if ( tokens[1] == "reinit" ) {
409 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
410 cout << "props: adding subsystem = " << tokens[i]
413 = args.getNode("subsystem", i-2, true);
414 node->setStringValue( tokens[i].c_str() );
416 } else if ( tokens[1] == "set-sea-level-air-temp-degc" ) {
417 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
418 cout << "props: set-sl command = " << tokens[i]
421 = args.getNode("temp-degc", i-2, true);
422 node->setStringValue( tokens[i].c_str() );
424 } else if ( tokens[1] == "set-outside-air-temp-degc" ) {
425 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
426 cout << "props: set-oat command = " << tokens[i]
429 = args.getNode("temp-degc", i-2, true);
430 node->setStringValue( tokens[i].c_str() );
432 } else if ( tokens[1] == "timeofday" ) {
433 for ( unsigned int i = 2; i < tokens.size(); ++i ) {
434 cout << "props: time of day command = " << tokens[i]
437 = args.getNode("timeofday", i-2, true);
438 node->setStringValue( tokens[i].c_str() );
440 } else if ( tokens[1] == "play-audio-message" ) {
441 if ( tokens.size() == 4 ) {
442 cout << "props: play audio message = " << tokens[2]
443 << " " << tokens[3] << endl;
444 SGPropertyNode *node;
445 node = args.getNode("path", 0, true);
446 node->setStringValue( tokens[2].c_str() );
447 node = args.getNode("file", 0, true);
448 node->setStringValue( tokens[3].c_str() );
451 if ( !globals->get_commands()
452 ->execute(tokens[1].c_str(), &args) )
454 SG_LOG( SG_NETWORK, SG_ALERT,
455 "Command " << tokens[1] << " failed.");
456 if ( mode == PROMPT ) {
459 push( getTerminator() );
462 if ( mode == PROMPT ) {
463 tmp += "<completed>";
465 push( getTerminator() );
469 if ( mode == PROMPT ) {
470 tmp += "no command specified";
472 push( getTerminator() );
475 } else if ( command == "quit" || command == "exit" ) {
479 } else if ( command == "data" ) {
481 } else if ( command == "prompt" ) {
483 } else if (callback_map.find(command) != callback_map.end() ) {
484 TelnetCallback t = callback_map[ command ];
488 error("No matching callback found for command:"+command);
490 else if ( command == "seti" ) {
491 if (tokens.size() == 3) {
492 node->getNode( tokens[1].c_str(), true )
493 ->setIntValue(atoi(tokens[2].c_str()));
495 const char* msg = "set init\r\n";
498 else if ( command == "setd" ) {
499 if (tokens.size() == 3) {
500 node->getNode( tokens[1].c_str(), true )
501 ->setDoubleValue(atof(tokens[2].c_str()));
504 else if ( command == "setb" ) {
505 if (tokens.size() == 3) {
506 if (tokens[2] == "false" || tokens[2] == "0") {
507 node->getNode( tokens[1].c_str(), true )
508 ->setBoolValue(false);
510 if (tokens[2] == "true" || tokens[2] == "1"){
511 node->getNode( tokens[1].c_str(), true )
512 ->setBoolValue(true);
516 else if ( command == "del" ) {
517 if (tokens.size() == 3){
518 node->getNode( tokens[1].c_str(), true )->removeChild(tokens[2].c_str(),0);
523 Valid commands are:\r\n\
525 cd <dir> cd to a directory, '..' to move back\r\n\
526 data switch to raw data mode\r\n\
527 dump dump current state (in xml)\r\n\
528 get <var> show the value of a parameter\r\n\
529 help show this help message\r\n\
530 ls [<dir>] list directory\r\n\
531 prompt switch to interactive mode (default)\r\n\
532 pwd display your current path\r\n\
533 quit terminate connection\r\n\
534 run <command> run built in command\r\n\
535 set <var> <val> set String <var> to a new <val>\r\n\
536 setb <var> <val> set Bool <var> to a new <val> only work with the foling value 0, 1, true, false\r\n\
537 setd <var> <val> set Double <var> to a new <val>\r\n\
538 seti <var> <val> set Int <var> to a new <val>\r\n\
539 del <var> <nod> delete <nod> in <var>\r\n\
540 subscribe <var> subscribe to property changes \r\n\
541 unscubscribe <var> unscubscribe from property changes (var must be the property name/path used by subscribe)\r\n";
546 } catch ( const string& msg ) {
547 string error = "-ERR \"" + msg + "\"";
548 push( error.c_str() );
549 push( getTerminator() );
552 if ( mode == PROMPT ) {
553 string prompt = node->getPath();
554 if (prompt.empty()) {
558 push( prompt.c_str() );
567 FGProps::FGProps( const vector<string>& tokens )
571 // props,medium,direction,hz,hostname,port#,style
572 if (tokens.size() == 2) {
573 port = atoi( tokens[1].c_str() );
574 set_hz( 5 ); // default to processing requests @ 5Hz
575 } else if (tokens.size() == 7) {
578 int hz = strtol( tokens[3].c_str(), &endptr, 10 );
580 SG_LOG( SG_IO, SG_ALERT, "I/O poll frequency out of range" );
581 set_hz( 5 ); // default to processing requests @ 5Hz
583 SG_LOG( SG_IO, SG_INFO, "Setting I/O poll frequency to "
587 port = atoi( tokens[5].c_str() );
589 throw FGProtocolConfigError( "FGProps: incorrect number of configuration arguments" );
608 SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
609 << "is already in use, ignoring" );
613 if (!simgear::NetChannel::open())
615 SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open network socket.");
619 int err = simgear::NetChannel::bind( "", port );
622 SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to open port #" << port << " - the port is already used (error " << err << ").");
626 err = simgear::NetChannel::listen( 5 );
629 SG_LOG( SG_IO, SG_ALERT, "FGProps: Failed to listen on port #" << port << "(error " << err << ").");
633 poller.addChannel(this);
635 SG_LOG( SG_IO, SG_INFO, "Props server started on port " << port );
647 SG_LOG( SG_IO, SG_INFO, "closing FGProps" );
665 FGProps::handleAccept()
667 simgear::IPAddress addr;
668 int handle = accept( &addr );
669 SG_LOG( SG_IO, SG_INFO, "Props server accepted connection from "
670 << addr.getHost() << ":" << addr.getPort() );
671 PropsChannel* channel = new PropsChannel();
672 channel->setHandle( handle );
673 poller.addChannel( channel );