Improved J661 support
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 ¶m) {
+ 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: