1
0
Fork 0

Bernie Bright:

Here is the new super improved telnet property interface.  CVS changelog
is at the end of this message.  Once this new telnet code is in and
compiles every where we can remove Network/props.[ch]xx.  I've added a
--telnet=<port> command line option to invoke the new server.  Later on
we could remove the --props option, or least change it to invoke the new
server.  I'll let you decide.

I've added some new commands to the telnet interface:

view next      Select the next view.
view prev      Select the previous view.
view set <n>   Select view 'n'
view get       Return index of current view

I'm not sure if these same effects could be achieved through property
operations.  The commands provide a convenient shortcut in any case.

I'm also planning on adding a panel command to manipulate panels and
objects contained therein (eg simulated mouse clicks).  There is going
to be some commonality with the command objects so we may need to
rationalize this in the near future.

Finally, I've also included my python stuff.  This is still very much a
work in progress, basically I've been using it to test the new telnet
server.  I have  tested it with python 2.2.  Feel free to add it to the
repository if you want.  I would suggest a scripts/python directory

CVS Changelog

Network/telnet.cxx: New property telnet protocol interface.  It supports
the same user interface provided by the --props server.  Additionally it
handles multiple simultaneous connections.  Added "view" command to
manipulate viewmgr.

Network/protocol.hxx: Added protocol configuration exception.

Main/fg_io.cxx:  Added new "telnet" protocol.  Added protocol
configuration parse exceptions.  Simplified protocol configuration
parsing.

Main/options.cxx: Added --telnet=<port> command line option and help
message.
This commit is contained in:
curt 2002-05-15 21:44:34 +00:00
parent 52c1cb2f7d
commit f9f05aa870
7 changed files with 772 additions and 170 deletions

View file

@ -35,6 +35,7 @@
#include <simgear/io/sg_socket_udp.hxx>
#include <simgear/math/sg_types.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/misc/strutils.hxx>
#include <Network/protocol.hxx>
#include <Network/atc610x.hxx>
@ -51,6 +52,7 @@
#include <Network/opengc.hxx>
#include <Network/nmea.hxx>
#include <Network/props.hxx>
#include <Network/telnet.hxx>
#include <Network/pve.hxx>
#include <Network/ray.hxx>
#include <Network/rul.hxx>
@ -68,30 +70,25 @@ io_container global_io_list;
// configure a port based on the config string
static FGProtocol *parse_port_config( const string& config )
{
bool short_circuit = false;
string::size_type begin, end;
begin = 0;
SG_LOG( SG_IO, SG_INFO, "Parse I/O channel request: " << config );
// determine protocol
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL; // dummy
vector<string> tokens = simgear::strutils::split( config, "," );
if (tokens.empty())
{
SG_LOG( SG_IO, SG_ALERT,
"Port configuration error: empty config string" );
return 0;
}
string protocol = config.substr(begin, end - begin);
begin = end + 1;
string protocol = tokens[0];
SG_LOG( SG_IO, SG_INFO, " protocol = " << protocol );
FGProtocol *io;
FGProtocol *io = 0;
try
{
if ( protocol == "atc610x" ) {
FGATC610x *atc610x = new FGATC610x;
io = atc610x;
short_circuit = true;
cout << "here ..." << endl;
return new FGATC610x;
} else if ( protocol == "atlas" ) {
FGAtlas *atlas = new FGAtlas;
io = atlas;
@ -105,17 +102,13 @@ static FGProtocol *parse_port_config( const string& config )
io = garmin;
} else if ( protocol == "httpd" ) {
// determine port
string port = config.substr(begin);
FGHttpd *httpd = new FGHttpd( atoi(port.c_str()) );
io = httpd;
short_circuit = true;
string port = tokens[1];
return new FGHttpd( atoi(port.c_str()) );
#ifdef FG_JPEG_SERVER
} else if ( protocol == "jpg-httpd" ) {
// determine port
string port = config.substr(begin);
FGJpegHttpd *jpeg_httpd = new FGJpegHttpd( atoi(port.c_str()) );
io = jpeg_httpd;
short_circuit = true;
string port = tokens[1];
return new FGJpegHttpd( atoi(port.c_str()) );
#endif
} else if ( protocol == "joyclient" ) {
FGJoyClient *joyclient = new FGJoyClient;
@ -133,8 +126,10 @@ static FGProtocol *parse_port_config( const string& config )
FGNMEA *nmea = new FGNMEA;
io = nmea;
} else if ( protocol == "props" ) {
FGProps *props = new FGProps;
io = props;
io = new FGProps();
} else if ( protocol == "telnet" ) {
io = new FGTelnet( tokens );
return io;
} else if ( protocol == "pve" ) {
FGPVE *pve = new FGPVE;
io = pve;
@ -147,93 +142,54 @@ static FGProtocol *parse_port_config( const string& config )
} else {
return NULL;
}
if ( ! short_circuit ) {
// determine medium
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL; // dummy
}
catch (FGProtocolConfigError& err)
{
SG_LOG( SG_IO, SG_ALERT, "Port configuration error: " << err.what() );
delete io;
return 0;
}
string medium = config.substr(begin, end - begin);
begin = end + 1;
string medium = tokens[1];
SG_LOG( SG_IO, SG_INFO, " medium = " << medium );
// determine direction
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL; // dummy
}
string direction = config.substr(begin, end - begin);
begin = end + 1;
string direction = tokens[2];
io->set_direction( direction );
SG_LOG( SG_IO, SG_INFO, " direction = " << direction );
// determine hertz
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL; // dummy
}
string hertz_str = config.substr(begin, end - begin);
begin = end + 1;
string hertz_str = tokens[3];
double hertz = atof( hertz_str.c_str() );
io->set_hz( hertz );
SG_LOG( SG_IO, SG_INFO, " hertz = " << hertz );
if ( medium == "serial" ) {
// device name
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL;
}
string device = config.substr(begin, end - begin);
begin = end + 1;
string device = tokens[4];
SG_LOG( SG_IO, SG_INFO, " device = " << device );
// baud
string baud = config.substr(begin);
string baud = tokens[5];
SG_LOG( SG_IO, SG_INFO, " baud = " << baud );
SGSerial *ch = new SGSerial( device, baud );
io->set_io_channel( ch );
} else if ( medium == "file" ) {
// file name
string file = config.substr(begin);
string file = tokens[4];
SG_LOG( SG_IO, SG_INFO, " file name = " << file );
SGFile *ch = new SGFile( file );
io->set_io_channel( ch );
} else if ( medium == "socket" ) {
// hostname
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL;
}
string hostname = tokens[4];
string port = tokens[5];
string style = tokens[6];
string hostname = config.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " hostname = " << hostname );
SG_LOG( SG_IO, SG_INFO, " port = " << port );
SG_LOG( SG_IO, SG_INFO, " style = " << style );
// port string
end = config.find(",", begin);
if ( end == string::npos ) {
return NULL;
}
string port = config.substr(begin, end - begin);
begin = end + 1;
SG_LOG( SG_IO, SG_INFO, " port string = " << port );
// socket style
string style_str = config.substr(begin);
SG_LOG( SG_IO, SG_INFO, " style string = " << style_str );
SGSocket *ch = new SGSocket( hostname, port, style_str );
io->set_io_channel( ch );
}
io->set_io_channel( new SGSocket( hostname, port, style ) );
}
return io;
@ -273,8 +229,6 @@ void fgIOInit() {
// process any serial port work
void fgIOProcess() {
FGProtocol *p;
// cout << "processing I/O channels" << endl;
static int inited = 0;
@ -292,9 +246,9 @@ void fgIOProcess() {
last = current;
}
for ( int i = 0; i < (int)global_io_list.size(); ++i ) {
for ( unsigned int i = 0; i < global_io_list.size(); ++i ) {
// cout << " channel = " << i << endl;
p = global_io_list[i];
FGProtocol* p = global_io_list[i];
if ( p->is_enabled() ) {
p->dec_count_down( interval );

View file

@ -1332,7 +1332,8 @@ int mainLoop( int argc, char **argv ) {
version = "unknown version";
#endif
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version "
<< version << endl );
<< version );
SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR );
// Allocate global data structures. This needs to happen before
// we parse command line options

View file

@ -24,7 +24,8 @@ libNetwork_a_SOURCES = \
pve.cxx pve.hxx \
raw_ctrls.hxx \
ray.cxx ray.hxx \
rul.cxx rul.hxx
rul.cxx rul.hxx \
telnet.cxx telnet.hxx
if OLD_AUTOMAKE
INCLUDES += -I$(top_srcdir) -I$(top_srcdir)/src

View file

@ -95,3 +95,14 @@ bool FGProtocol::parse_message() {
}
void FGProtocol::set_direction( const string& d ) {
if ( d == "in" ) {
dir = SG_IO_IN;
} else if ( d == "out" ) {
dir = SG_IO_OUT;
} else if ( d == "bi" ) {
dir = SG_IO_BI;
} else {
dir = SG_IO_NONE;
}
}

View file

@ -26,6 +26,7 @@
#include <simgear/compiler.h>
#include <simgear/io/iochannel.hxx>
#include STL_STRING
#include <vector>
@ -36,10 +37,6 @@ SG_USING_STD(vector);
#define FG_MAX_MSG_SIZE 16384
// forward declaration
class SGIOChannel;
class FGProtocol {
private:
@ -68,17 +65,7 @@ public:
virtual bool close();
inline SGProtocolDir get_direction() const { return dir; }
inline void set_direction( const string& d ) {
if ( d == "in" ) {
dir = SG_IO_IN;
} else if ( d == "out" ) {
dir = SG_IO_OUT;
} else if ( d == "bi" ) {
dir = SG_IO_BI;
} else {
dir = SG_IO_NONE;
}
}
void set_direction( const string& d );
inline double get_hz() const { return hz; }
inline void set_hz( double t ) { hz = t; }
@ -108,6 +95,18 @@ typedef vector < FGProtocol * > io_container;
typedef io_container::iterator io_iterator;
typedef io_container::const_iterator const_io_iterator;
#include <stdexcept>
SG_USING_STD(invalid_argument);
//namespace flightgear { namespace network {
class FGProtocolConfigError : public invalid_argument
{
public:
FGProtocolConfigError( const string& what_string )
: invalid_argument(what_string) {}
};
//}} // end namespace flightgear::network
#endif // _PROTOCOL_HXX

547
src/Network/telnet.cxx Normal file
View file

@ -0,0 +1,547 @@
// \file telnet.cx
// Property telnet server class.
//
// Written by Bernie Bright, started May 2002.
//
// Copyright (C) 2002 Bernie Bright - bbright@bigpond.net.au
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/props.hxx>
#include <simgear/misc/props_io.hxx>
#include STL_STRSTREAM
#include <Main/globals.hxx>
#include <Main/viewmgr.hxx>
#include <plib/netChat.h>
#include "telnet.hxx"
#if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
SG_USING_STD(strstream);
#endif
/**
* Telnet connection class.
* This class represents a connection to a telnet-style client.
*/
class TelnetChannel : public netChat
{
netBuffer buffer;
/**
* Current property node name.
*/
string path;
enum Mode {
PROMPT,
DATA
};
Mode mode;
public:
/**
* Constructor.
*/
TelnetChannel();
/**
* Append incoming data to our request buffer.
*
* @param s Character string to append to buffer
* @param n Number of characters to append.
*/
void collectIncomingData( const char* s, int n );
/**
* Process a complete request from the telnet client.
*/
void foundTerminator();
private:
/**
* Return a "Node no found" error message to the client.
*/
void node_not_found_error( const string& node_name );
void view_cmd( const vector<string>& );
};
/**
*
*/
TelnetChannel::TelnetChannel()
: buffer(512),
path("/"),
mode(PROMPT)
{
setTerminator( "\r\n" );
}
/**
*
*/
void
TelnetChannel::collectIncomingData( const char* s, int n )
{
buffer.append( s, n );
}
/**
*
*/
void
TelnetChannel::node_not_found_error( const string& node_name )
{
string error = "ERR Node \"";
error += node_name;
error += "\" not found.";
push( error.c_str() );
push( getTerminator() );
}
// return a human readable form of the value "type"
static string
getValueTypeString( const SGPropertyNode *node )
{
string result;
if ( node == NULL )
{
return "unknown";
}
SGPropertyNode::Type type = node->getType();
if ( type == SGPropertyNode::UNSPECIFIED ) {
result = "unspecified";
} else if ( type == SGPropertyNode::NONE ) {
result = "none";
} else if ( type == SGPropertyNode::BOOL ) {
result = "bool";
} else if ( type == SGPropertyNode::INT ) {
result = "int";
} else if ( type == SGPropertyNode::LONG ) {
result = "long";
} else if ( type == SGPropertyNode::FLOAT ) {
result = "float";
} else if ( type == SGPropertyNode::DOUBLE ) {
result = "double";
} else if ( type == SGPropertyNode::STRING ) {
result = "string";
}
return result;
}
/**
* We have a command.
*
* TODO: possible future commands:
* panel <subcmd>
* panel load [path]
* panel mouse <button> up|down|click <x> <y>
* panel visible 0|1
* panel height -> h, Retrieve panel height
* panel width -> w, Retrieve panel width
* panel xoffset -> x, Retrieve panel x offset
* panel yoffset -> y, Retrieve panel y offset
*
* property <subcmd>
* property toggle <prop>
* property adjust <prop> <step> <offset> <factor> <min> <max> <wrap>
* property multiply <prop> <factor>
* property swap <prop1> <prop2>
* property scale <prop> <setting> <offset> <factor>
*
* view <subcmd>
* view next
* view prev
* view set <n>
* view current -> n, Retrieve index of current view
*/
void
TelnetChannel::foundTerminator()
{
const char* cmd = buffer.getData();
SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );
vector<string> tokens = simgear::strutils::split( cmd );
SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );
if (!tokens.empty())
{
string command = tokens[0];
if (command == "ls")
{
SGPropertyNode* dir = node;
if (tokens.size() == 2)
{
if (tokens[1][0] == '/')
{
dir = globals->get_props()->getNode( tokens[1].c_str() );
}
else
{
string s = path;
s += "/";
s += tokens[1];
dir = globals->get_props()->getNode( s.c_str() );
}
if (dir == 0)
{
node_not_found_error( tokens[1] );
goto prompt;
}
}
for (int i = 0; i < dir->nChildren(); i++)
{
SGPropertyNode * child = dir->getChild(i);
string name = child->getName();
string line = name;
if (dir->getChild( name.c_str(), 1 ))
{
char buf[16];
sprintf(buf, "[%d]", child->getIndex());
line += buf;
}
if ( child->nChildren() > 0 )
{
line += "/";
}
else
{
if (mode == PROMPT)
{
string value = dir->getStringValue( name.c_str(), "" );
line += " =\t'" + value + "'\t(";
line += getValueTypeString(
dir->getNode( name.c_str() ) );
line += ")";
}
}
line += getTerminator();
push( line.c_str() );
}
}
else if ( command == "dump" )
{
strstream buf;
if ( tokens.size() <= 1 )
{
writeProperties( buf, node );
push( buf.str() );
push( getTerminator() );
}
else
{
SGPropertyNode *child = node->getNode( tokens[1].c_str() );
if ( child )
{
writeProperties ( buf, child );
push( buf.str() );
push( getTerminator() );
}
else
{
node_not_found_error( tokens[1] );
}
}
}
else if ( command == "cd" )
{
if (tokens.size() == 2)
{
try
{
SGPropertyNode* child = node->getNode( tokens[1].c_str() );
if ( child )
{
node = child;
path = node->getPath();
}
else
{
node_not_found_error( tokens[1] );
}
}
catch (...)
{
// Ignore attempt to move past root node with ".."
}
}
}
else if ( command == "pwd" )
{
string ttt = node->getPath();
if (ttt.empty())
{
ttt = "/";
}
push( ttt.c_str() );
push( getTerminator() );
}
else if ( command == "get" || command == "show" )
{
if ( tokens.size() == 2 )
{
string tmp;
string value = node->getStringValue ( tokens[1].c_str(), "" );
if ( mode == PROMPT )
{
tmp = tokens[1];
tmp += " = '";
tmp += value;
tmp += "' (";
tmp += getValueTypeString(
node->getNode( tokens[1].c_str() ) );
tmp += ")\n";
}
else
{
tmp = value + "\n";
}
push( tmp.c_str() );
}
}
else if ( command == "set" )
{
if ( tokens.size() == 3 )
{
node->getNode( tokens[1].c_str(), true )->setStringValue(tokens[2].c_str());
if ( mode == PROMPT )
{
// now fetch and write out the new value as confirmation
// of the change
string value = node->getStringValue ( tokens[1].c_str(), "" );
string tmp = tokens[1] + " = '" + value + "' (";
tmp += getValueTypeString( node->getNode( tokens[1].c_str() ) );
tmp += ")\n";
push( tmp.c_str() );
}
}
}
else if (command == "quit")
{
close();
shouldDelete();
return;
}
else if ( command == "data" )
{
mode = DATA;
}
else if ( command == "prompt" )
{
mode = PROMPT;
}
else if ( command == "view" )
{
view_cmd( tokens );
}
// else if ( command == "panel" )
// {
// panel_cmd( tokens );
// }
// else if ( command == "property" )
// {
// property_cmd( tokens );
// }
else
{
const char* msg = "
Valid commands are:
cd <dir> cd to a directory, '..' to move back
data switch to raw data mode
dump dump current state (in xml)
get <var> show the value of a parameter
help show this help message
ls [<dir>] list directory
prompt switch to interactive mode (default)
pwd display your current path
quit terminate connection
set <var> <val> set <var> to a new <val>
show <var> synonym for get
view next display next view
view prev display prev view
view set <n> display view 'n'
view get return current view index
view current return current view index
";
push( msg );
}
}
prompt:
if (mode == PROMPT)
{
string prompt = node->getPath();
if (prompt.empty())
{
prompt = "/";
}
prompt += "> ";
push( prompt.c_str() );
}
buffer.remove();
}
/**
*
*/
void
TelnetChannel::view_cmd( const vector<string>& tokens )
{
if (tokens.size() <= 1)
{
// ERROR: no sub-command
return;
}
string subcmd = tokens[1];
if (subcmd == "next")
{
globals->get_current_view()->setHeadingOffset_deg(0.0);
globals->get_viewmgr()->next_view();
}
else if (subcmd == "prev")
{
globals->get_current_view()->setHeadingOffset_deg(0.0);
globals->get_viewmgr()->prev_view();
}
else if (subcmd == "set")
{
if (tokens.size() == 3)
{
int i = atoi( tokens[2].c_str() );
if (0 >= i && i < globals->get_viewmgr()->size())
{
globals->get_current_view()->setHeadingOffset_deg(0.0);
globals->get_viewmgr()->set_view(i);
globals->get_viewmgr()->copyToCurrent();
}
}
}
else if (subcmd == "get" || subcmd == "current")
{
int i = globals->get_viewmgr()->get_current();
char buf[16];
snprintf( buf, sizeof(buf), "%d", i );
push( buf );
push( getTerminator() );
}
else
{
// ERROR: invalid subcommand.
}
}
/**
*
*/
FGTelnet::FGTelnet( const vector<string>& tokens )
{
if (tokens.size() != 2)
{
throw FGProtocolConfigError( "FGProps: expected 1 argument, <port>" );
}
port = atoi( tokens[1].c_str() );
}
/**
*
*/
FGTelnet::~FGTelnet()
{
}
/**
*
*/
bool
FGTelnet::open()
{
if ( is_enabled() )
{
SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
<< "is already in use, ignoring" );
return false;
}
netChannel::open();
netChannel::bind( "", port );
netChannel::listen( 5 );
SG_LOG( SG_IO, SG_INFO, "Telnet server started on port " << port );
set_hz( 5 ); // default to processing requests @ 5Hz
set_enabled( true );
return true;
}
/**
*
*/
bool
FGTelnet::close()
{
return true;
}
/**
*
*/
bool
FGTelnet::process()
{
netChannel::poll();
return true;
}
/**
*
*/
void
FGTelnet::handleAccept()
{
netAddress addr;
int handle = accept( &addr );
SG_LOG( SG_IO, SG_INFO, "Telnet server accepted connection from "
<< addr.getHost() << ":" << addr.getPort() );
TelnetChannel* channel = new TelnetChannel();
channel->setHandle( handle );
}

89
src/Network/telnet.hxx Normal file
View file

@ -0,0 +1,89 @@
// \file telnet.hxx
// Property server class.
//
// Written by Bernie Bright, started May 2002.
//
// Copyright (C) 2002 Bernie Bright - bbright@bigpond.net.au
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// $Id$
#ifndef TELNET_HXX_INCLUDED
#define TELNET_HXX_INCLUDED 1
#include <simgear/compiler.h>
#include STL_STRING
#include <vector>
SG_USING_STD(string);
SG_USING_STD(vector);
#include <plib/netChannel.h>
#include "protocol.hxx"
/**
* Property server class.
* This class provides a telnet-like server for remote access to
* FlightGear properties.
*/
class FGTelnet : public FGProtocol,
public netChannel
{
private:
/**
* Server port to listen on.
*/
int port;
public:
/**
* Create a new TCP server.
*
* @param tokens Tokenized configuration parameters
*/
FGTelnet( const vector<string>& tokens );
/**
* Destructor.
*/
~FGTelnet();
/**
* Start the telnet server.
*/
bool open();
/**
* Process network activity.
*/
bool process();
/**
*
*/
bool close();
/**
* Accept a new client connection.
*/
void handleAccept();
};
#endif //TELNET_HXX_INCLUDED