Improved J661 support

From FlightGear wiki
Revision as of 14:45, 2 May 2012 by Hooray (talk | contribs)
Jump to navigation Jump to search

Background

Establish the interface requirements for better J661 support, so that J661 and FlightGear can collaborate in a more efficient, and overall better, fashion. So that J661 may connect to a running FlightGear instance in order to use simulated flight data to drive avionics simulated by j661.

Requirements

  • use the existing telnet/props interface
  • do not touch existing behavior
  • make the protocol more efficient to get/set properties
  • i.e. avoid polling
  • provide some form of push/pull support

Patch prototype

    diff --git a/src/Network/props.cxx b/src/Network/props.cxx
    index 7240d5c..bd014db 100644
    --- a/src/Network/props.cxx
    +++ b/src/Network/props.cxx
    @@ -33,6 +33,7 @@
    #include <simgear/misc/strutils.hxx>
    #include <simgear/props/props.hxx>
    #include <simgear/props/props_io.hxx>
    +#include <simgear/structure/exception.hxx>

    #include <sstream>
    #include <iostream>
    @@ -45,6 +46,12 @@

    #include "props.hxx"

    +#include <map>
    +#include <vector>
    +#include <string>
    +
    +#include <boost/functional/hash.hpp> // for property hashing (should probably be replaced eventually)
    +
    using std::stringstream;
    using std::ends;

    @@ -55,7 +62,7 @@ using std::endl;
      * Props connection class.
      * This class represents a connection to props client.
      */
    -class PropsChannel : public simgear::NetChat
    +class PropsChannel : public simgear::NetChat, public SGPropertyChangeListener
    {
         simgear::NetBuffer buffer;

    @@ -75,6 +82,7 @@ public:
          * Constructor.
          */
         PropsChannel();
    +    ~PropsChannel();

         /**
          * Append incoming data to our request buffer.
    @@ -89,10 +97,38 @@ public:
          */
         void foundTerminator();

    +    // callback for registered listeners (subscriptions)
    +    void valueChanged(SGPropertyNode *node);
    private:
    +
    +    typedef std::vector<std::string> ParameterList;
    +
    +
         inline void node_not_found_error( const string& s ) const {
             throw "node '" + s + "' not found";
         }
    +
    +    void error(std::string msg) {  // wrapper: prints errors to STDERR and to the telnet client
    +       push( msg.c_str() ); push( getTerminator() );
    +       SG_LOG(SG_GENERAL, SG_ALERT, __FILE__<<"@" << __LINE__ <<" in " << __FUNCTION__ <<":"<< msg.c_str() << std::endl);
    +    }
    +
    +
    +    bool check_args(ParameterList tok, unsigned int num, const char* func) {
    +       if (tok.size()-1 < num) {
    +          error(string("Wrong argument count for:")+string(func) );
    +          return false;
    +       }
    +       return true;
    +    }
    +
    +    std::vector<SGPropertyNode_ptr> _listeners;
    +    typedef void (PropsChannel::*TelnetCallback) (const ParameterList&);
    +    std::map<std::string, TelnetCallback> callback_map;
    +
    +    // callback implementations:
    +    void subscribe(const ParameterList &p);
    +    void unsubscribe(const ParameterList &p);
    };

    /**
    @@ -104,6 +140,67 @@ PropsChannel::PropsChannel()
           mode(PROMPT)
    {
         setTerminator( "\r\n" );
    +    callback_map["subscribe"]    =    &PropsChannel::subscribe;
    +    callback_map["unsubscribe"]   =   &PropsChannel::unsubscribe;
    +}
    +
    +PropsChannel::~PropsChannel() {
    +  // clean up all registered listeners   
    +  for (unsigned int i=0;i< _listeners.size(); i++)
    +    _listeners[i]->removeChangeListener( this  );
    +
    +}
    +
    +void PropsChannel::subscribe(const ParameterList& param) {
    +   if (! check_args(param,1,"subscribe")) return;
    +
    +   std::string command = param[0];
    +   const char* p = param[1].c_str();
    +   if (!p) return;
    +
    +        SG_LOG(SG_GENERAL, SG_ALERT, p << std::endl);
    +        push( command.c_str() ); push ( " " );
    +        push( p );
    +        push( getTerminator() );
    +
    +        SGPropertyNode *n = globals->get_props()->getNode( p,true );
    +   if ( n->isTied() ) {
    +      error("Error:Tied properties cannot register listeners");
    +      return;
    +   }
    +    if (n) {
    +         n->addChangeListener( this );
    +    std::stringstream hashcode;
    +    boost::hash<std::string> string_hash;
    +    hashcode << "id:" << string_hash(n->getPath()) << getTerminator();
    +    // push( hashcode.str().c_str() );  // not yet very useful
    +    _listeners.push_back( n ); // housekeeping, save for deletion in dtor later on
    +         }
    +         else {
    +       error("listener could not be added");
    +         }
    +
    +
    +}
    +
    +void PropsChannel::unsubscribe(const ParameterList &param) {
    +  if (!check_args(param,1,"unsubscribe")) return;
    +
    +  try {
    +   SGPropertyNode *n = globals->get_props()->getNode( param[1].c_str() );
    +   n->removeChangeListener( this );
    +  } catch (sg_exception &e) {
    +     error("Error:Listener could not be removed");
    +  }
    +}
    +
    +
    +//TODO: provide support for different types of subscriptions MODES ? (child added/removed, thesholds, min/max)
    +void PropsChannel::valueChanged(SGPropertyNode* ptr) {
    +  SG_LOG(SG_GENERAL, SG_ALERT, __FILE__<< "@"<<__LINE__ << ":" << __FUNCTION__ << std::endl); 
    +  std::stringstream response;
    +  response << ptr->getPath(true) << "=" <<  ptr->getStringValue() << "\r\n"; //TODO: use hashes, echo several properties at once, use proper terminator
    +  push( response.str().c_str() );
    }

    /**
    @@ -160,7 +257,7 @@ PropsChannel::foundTerminator()
         const char* cmd = buffer.getData();
         SG_LOG( SG_IO, SG_INFO, "processing command = \"" << cmd << "\"" );

    -    vector<string> tokens = simgear::strutils::split( cmd );
    +    ParameterList tokens = simgear::strutils::split( cmd );

         SGPropertyNode* node = globals->get_props()->getNode( path.c_str() );

    @@ -292,7 +389,7 @@ PropsChannel::foundTerminator()
                         if ( !globals->get_commands()
                                  ->execute( "reinit", &args) )
                         {
    -                        SG_LOG( SG_NETWORK, SG_ALERT,
    +                        SG_LOG( SG_GENERAL, SG_ALERT,
                                     "Command " << tokens[1] << " failed.");
                             if ( mode == PROMPT ) {
                                 tmp += "*failed*";
    @@ -357,7 +454,7 @@ PropsChannel::foundTerminator()
                         if ( !globals->get_commands()
                                  ->execute(tokens[1].c_str(), &args) )
                         {
    -                        SG_LOG( SG_NETWORK, SG_ALERT,
    +                        SG_LOG( SG_GENERAL, SG_ALERT,
                                     "Command " << tokens[1] << " failed.");
                             if ( mode == PROMPT ) {
                                 tmp += "*failed*";
    @@ -386,7 +483,14 @@ PropsChannel::foundTerminator()
                     mode = DATA;
                 } else if ( command == "prompt" ) {
                     mode = PROMPT;
    -            } else {
    +            } else if (command  == "subscribe" || command == "unsubscribe") {
    +         TelnetCallback t = callback_map[ command.c_str() ]; //FIXME: use map lookup for condition
    +         if (t)
    +                 (this->*t) (tokens);
    +         else
    +            error("No matching callback found for command:"+command);
    +       }
    +       else {
                     const char* msg = "\
    Valid commands are:\r\n\
    \r\n\
    @@ -400,7 +504,9 @@ prompt             switch to interactive mode (default)\r\n\
    pwd                display your current path\r\n\
    quit               terminate connection\r\n\
    run <command>      run built in command\r\n\
    -set <var> <val>    set <var> to a new <val>\r\n";
    +set <var> <val>    set <var> to a new <val>\r\n\
    +subscribe <var>      subscribe to property changes (returns hash code for property)\r\n\
    +unscubscribe <id>  unscubscribe from property changes (id must be the property hash as returned from subscribe command)\r\n";
                     push( msg );
                 }
             }
    diff --git a/src/Network/props.hxx b/src/Network/props.hxx
    index e55f7c1..09b5a09 100644
    --- a/src/Network/props.hxx
    +++ b/src/Network/props.hxx
    @@ -40,7 +40,8 @@
      * FlightGear properties.
      */
    class FGProps : public FGProtocol,
    -      public simgear::NetChannel
    +      public simgear::NetChannel,
    +      public SGPropertyChangeListener // for subscriptions
    {
    private:

Suggested modifications and enhancements

Related Discussions